Cysharp / MemoryPack

Zero encoding extreme performance binary serializer for C# and Unity.
MIT License
2.99k stars 181 forks source link

Serialize in Roslyn. #285

Closed whaqzhzd closed 2 months ago

whaqzhzd commented 2 months ago

I am trying to serialize in Roslyn. I am generating C# source code in a .cs file, compiling the code using CSharpCompilation, and obtaining an Assembly to call functions inside it. Now I have encountered a MemoryPackSerializationException. Does this mean I cannot use MemoryPack in this way?

 var dotNetCoreDir = Path.GetDirectoryName(typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly.Location)!;

 var refPaths = new[] {
     typeof(object).GetTypeInfo().Assembly.Location,
     typeof(Console).GetTypeInfo().Assembly.Location,
     Path.Combine(dotNetCoreDir, "System.Runtime.dll"),
     Path.Combine(dotNetCoreDir, "System.Core.dll"),
     Path.Combine(dotNetCoreDir, "System.Linq.dll"),
     Path.Combine(Environment.CurrentDirectory, "MemoryPack.Core.dll")
 };

 var references = refPaths.Select(r => MetadataReference.CreateFromFile(r)).ToArray();

 var compilation = CSharpCompilation.Create(
                                         null,
                                         [CSharpSyntaxTree.ParseText(source)],
                                         new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) },
                                         new CSharpCompilationOptions(OutputKind.ConsoleApplication)).AddReferences(references);

 using var memSteam = new MemoryStream();

 var emitResult = compilation.Emit(memSteam);

 if (!emitResult.Success || emitResult.Diagnostics.FirstOrDefault(x => x.Severity > 0) != null)
 {
     Console.WriteLine(emitResult.Diagnostics[0].ToString());
     throw new ArgumentException();
 }

 memSteam.Seek(0, SeekOrigin.Begin);
 return Assembly.Load(memSteam.ToArray());
neuecc commented 2 months ago

I don't know what you are trying to serialize, but the target class must be code generated by Source Generator. Looking at the reference to that assembly, perhaps Source Generator is not working.

whaqzhzd commented 2 months ago

It is very simple to say, in fact, through Roslyn to dynamically build a code string, similar to this source string.

using MemoryPack;

namespace Example
{
    [MemoryPackable]
    public partial class Person
    {
        public int Age { get;   set;   }
        public string Name { get;   set;   }
    }

    public class Program
    {
        public static void Main()
        {
            var v = new Person { Age = 40, Name = "John" };
            var bin = MemoryPackSerializer.Serialize(v);
            // do samething
        }
    }
}

var b = GetAssemblyFromSourceByRoslyn(source);

private Assembly GetAssemblyFromSourceByRoslyn(string source)
 {
     var dotNetCoreDir = Path.GetDirectoryName(typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly.Location)!;

     var refPaths = new[] {
         typeof(object).GetTypeInfo().Assembly.Location,
         typeof(Console).GetTypeInfo().Assembly.Location,
         Path.Combine(dotNetCoreDir,"netstandard.dll"),
         Path.Combine(dotNetCoreDir, "System.Runtime.dll"),
         Path.Combine(dotNetCoreDir, "System.Core.dll"),
         Path.Combine(dotNetCoreDir, "System.Linq.dll"),
         Path.Combine(dotNetCoreDir, "System.IO.dll"),
         Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MessagePack.Annotations.dll"),
         Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MessagePack.dll")
     };

     var references = refPaths.Select(r => MetadataReference.CreateFromFile(r)).ToArray();
     var compilation = CSharpCompilation.Create(
                                             myTI.ToTitleCase(csName),
                                             [CSharpSyntaxTree.ParseText(source)],
                                             new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) },
                                             new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)).AddReferences(references);

     using var memSteam = new MemoryStream();

     var emitResult = compilation.Emit(memSteam);

     if (!emitResult.Success || emitResult.Diagnostics.FirstOrDefault(x => x.Severity > 0) != null)
     {
         Console.WriteLine(emitResult.Diagnostics[0].ToString());
         throw new Exception();
     }

     memSteam.Seek(0, SeekOrigin.Begin);
     return Assembly.Load(memSteam.ToArray());
 }

var methodB = b.CreateInstance("ExampleProgram")! .GetType().GetMethod("Main")! ;

// error MemoryPackSerializationException methodB.Invoke(null, null)! ;

SunSi12138 commented 1 month ago

hey, how do you solve it.

I learned that you can manually call the source generator by CSharpGeneratorDriver.Create to load and RunGeneratorsAndUpdateCompilation to update compilation

I work out this: `using System.Collections; using System.Numerics; using System.Runtime.Loader; using System.Text.Json; using System.Text.Json.Serialization; using MemoryPack; using MemoryPack.Generator; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp;

var classStr = @"using MemoryPack; using System.Text.Json.Serialization;

[MemoryPackable] public partial class Tes { [JsonPropertyName(""姓名"")]
public string Name { get; set; } public string Address { get; set; } }";

var type = CreateDynamicType("Tes", classStr);

// Serialize var obj = Activator.CreateInstance(type); var property = type.GetProperty("Name"); property.SetValue(obj, "张三"); var json = JsonSerializer.Serialize(obj); var binary = MemoryPackSerializer.Serialize(type,obj);

Console.WriteLine(json); Console.WriteLine(binary.Length);

// Roslyn Compile Type CreateDynamicType(string typeName, string classStr) {

var syntaxTree = CSharpSyntaxTree.ParseText(classStr);
var baseDirectory = typeof(object).Assembly.Location.Replace("System.Private.CoreLib.dll", "");
var references = new[]
{
    MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
    MetadataReference.CreateFromFile(typeof(Uri).Assembly.Location),
    MetadataReference.CreateFromFile(typeof(BigInteger).Assembly.Location),
    MetadataReference.CreateFromFile(typeof(BitArray).Assembly.Location),
    MetadataReference.CreateFromFile($"{baseDirectory}System.Runtime.dll"),
    MetadataReference.CreateFromFile($"{baseDirectory}System.Runtime.dll"),
    MetadataReference.CreateFromFile(typeof(MemoryPackableAttribute).Assembly.Location),
    MetadataReference.CreateFromFile(typeof(JsonPropertyNameAttribute).Assembly.Location),
};
var compilation = CSharpCompilation.Create("Model", [syntaxTree])
    .WithReferences(references)
    .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

// Call MemoryPack SourceGenerator
var generatorDriver = CSharpGeneratorDriver.Create(
    new MemoryPackGenerator()
);
generatorDriver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out var diagnostics);

if (diagnostics.Any())
{
    foreach (var item in diagnostics)
    {
        Console.WriteLine(item);
    }
    throw new Exception("compile error");
}

diagnostics = newCompilation.GetDiagnostics();
if(diagnostics.Any())
{
    foreach (var item in diagnostics)
    {
        Console.WriteLine(item);
    }
    throw new Exception("compile error");
}
// write to file
var path = Path.Combine(Directory.GetCurrentDirectory(), "Model2.dll");
var result = newCompilation.Emit(path);
if (!result.Success)
{
    throw new Exception("compile error");
}
// load assembly
// var assembly = new AssemblyLoadContext("testLoader").LoadFromAssemblyPath("/Users/sunsi/Desktop/Test/Test/Model.dll");
var assembly = new AssemblyLoadContext("testLoader").LoadFromAssemblyPath("/Users/sunsi/Desktop/Test/Test/Model2.dll");
var assemblyType = assembly.GetType(typeName);
return assemblyType!;

} `

but the ide find out no Generator namespace or MemoryPackGenerator class , I suspect that the issue is due to the settings of the MemoryPack.Generator.csproj project.

SunSi12138 commented 1 month ago

@neuecc Could you please take another look at this issue for me? Really appreciate it

SunSi12138 commented 3 weeks ago

After all, the runtime compilation solution with Roslyn was given up. Compile with dotnet publish through process will perfectly support all the feature