Home > Back-end >  System.PlatformNotSupportedException Compiling C# code at runtime .NET Core
System.PlatformNotSupportedException Compiling C# code at runtime .NET Core

Time:09-21

Trying to compile simple C# code at runtime on .NET Core but have this error:

System.PlatformNotSupportedException: 'Operation is not supported on this platform.'

on this line:

CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);

My code:

using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Text;

string code = @"
    using System;

    namespace First
    {
        public class Program
        {
            public static void Main()
            {
            "  
                "Console.WriteLine(\"Hello, world!\");"
                  @"
            }
        }
    }
";


CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();

parameters.ReferencedAssemblies.Add("System.Drawing.dll");
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = true;

CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);


if (results.Errors.HasErrors)
{
    StringBuilder sb = new StringBuilder();

    foreach (CompilerError error in results.Errors)
    {
        sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
    }

    throw new InvalidOperationException(sb.ToString());
}


Assembly assembly = results.CompiledAssembly;
Type program = assembly.GetType("First.Program");
MethodInfo main = program.GetMethod("Main");


main.Invoke(null, null);

CodePudding user response:

I recommend using the Roslyn compiler. You'll need to add references Microsoft.CodeAnalysis and Microsoft.CodeAnalysis.CSharp for the following example to work. Note, that the RoslynCompiler class loads the assembly dynamically. You can modify the class fairly easily to use a FileStream instead of a MemoryStream if you want to save the compilation to disk for reuse.

Sample Usage of RoslynCompiler Class (below)

    string code = @"
    using System;

    namespace First
    {
        public class Program
        {
            public static void Main()
            {
                Console.WriteLine(\"Hello, world!\");
            }
            public static void WithParams(string message)
            {
                Console.WriteLine(message);
            }
        }
    }
";

var compiler = new RoslynCompiler("First.Program", code, new[] {typeof(Console)});
var type = compiler.Compile();
    
type.GetMethod("Main").Invoke(null, null);
//result: Hellow World!

// pass an object array to the second null parameter to pass arguments
type.GetMethod("WithParams").Invoke(null, new object[] {"Hi there from invoke!"});
    //result: Hi from invoke

Roslyn Compiler Class (Quick and Dirty Example)

public class RoslynCompiler
{
    readonly CSharpCompilation _compilation;
    Assembly _generatedAssembly;
    Type? _proxyType;
    string _assemblyName;
    string _typeName;
    
    public RoslynCompiler(string typeName, string code, Type[] typesToReference)
    {
        _typeName = typeName;
        var refs = typesToReference.Select(h => MetadataReference.CreateFromFile(h.Assembly.Location) as MetadataReference).ToList();
        
        //some default refeerences
        refs.Add(MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly.Location), "System.Runtime.dll")));
        refs.Add(MetadataReference.CreateFromFile(typeof(Object).Assembly.Location));

       //generate syntax tree from code and config compilation options
        var syntax = CSharpSyntaxTree.ParseText(code);
        var options = new CSharpCompilationOptions(
            OutputKind.DynamicallyLinkedLibrary, 
            allowUnsafe: true,
            optimizationLevel: OptimizationLevel.Release);

        _compilation = CSharpCompilation.Create(_assemblyName = Guid.NewGuid().ToString(), new List<SyntaxTree> { syntax }, refs, options);
    }

    public Type Compile()
    {
        
        if (_proxyType != null) return _proxyType;
        
        using (var ms = new MemoryStream())
        {
            var result = _compilation.Emit(ms);
            if (!result.Success)
            {
                var compilationErrors = result.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error)
                    .ToList();
                if (compilationErrors.Any())
                {
                    var firstError = compilationErrors.First();
                    var errorNumber = firstError.Id;
                    var errorDescription = firstError.GetMessage();
                    var firstErrorMessage = $"{errorNumber}: {errorDescription};";
                    var exception = new Exception($"Compilation failed, first error is: {firstErrorMessage}");
                    compilationErrors.ForEach(e => { if (!exception.Data.Contains(e.Id)) exception.Data.Add(e.Id, e.GetMessage()); });
                    throw exception;
                }
            }
            ms.Seek(0, SeekOrigin.Begin);

            _generatedAssembly = AssemblyLoadContext.Default.LoadFromStream(ms);

            _proxyType = _generatedAssembly.GetType(_typeName);
            return _proxyType;
        }
    }
}

Performance Tip

If performance matters, use delegates as opposed to Invoke as follows to achieve near pre-compiled throughput:

void Main()
{
    string code = @"OMITTED EXAMPLE CODE FROM SAMPLE ABOVE";

    var compiler = new RoslynCompiler("First.Program", code, new[] { typeof(Console) });
    var type = compiler.Compile();  
    
    // If perf matters used delegates to get near pre-compiled througput vs Invoke()
    var cachedDelegate = new DynamicDelegateCacheExample(type); 
    
    cachedDelegate.Main();
    //result: Hellow world!
    
    cachedDelegate.Main("Hi there from cached delegate!");
    //result: Hi there from cached delegate!


}
public class DynamicDelegateCacheExample
{
    delegate void methodNoParams();
    delegate void methodWithParamas(string message);
    private static methodNoParams cachedDelegate;
    private static methodWithParamas cachedDelegateWeithParams;

    public DynamicDelegateCacheExample(Type myDynamicType)
    {
        cachedDelegate = myDynamicType.GetMethod("Main").CreateDelegate<methodNoParams>();
        cachedDelegateWeithParams = myDynamicType.GetMethod("WithParams").CreateDelegate<methodWithParamas>();
    }
    
    public void Main() => cachedDelegate();

    public void Main(string message) => cachedDelegateWeithParams(message);
}

CodePudding user response:

With .net core netstandard and publishing to a self contained exe there are a couple more tricks you'll need;

public static ModuleMetadata GetMetadata(this Assembly assembly)
{
    // based on https://github.com/dotnet/runtime/issues/36590#issuecomment-689883856
    unsafe
    {
        return assembly.TryGetRawMetadata(out var blob, out var len)
            ? ModuleMetadata.CreateFromMetadata((IntPtr)blob, len)
            : throw new InvalidOperationException($"Could not get metadata from {assembly.FullName}");
    }
}

#pragma warning disable IL3000
public static MetadataReference GetReference(this Assembly assembly)
    => (assembly.Location == "")
        ? AssemblyMetadata.Create(assembly.GetMetadata()).GetReference()
        : MetadataReference.CreateFromFile(assembly.Location);
#pragma warning restore IL3000

public static Assembly Compile(string source, IEnumerable<Type> references)
{
    var refs = new HashSet<Assembly>(){
        typeof(object).Assembly
    };
    foreach (var t in references)
        refs.Add(t.Assembly);

    foreach (var a in AppDomain.CurrentDomain.GetAssemblies()
        .Where(a => !a.IsDynamic
            && a.ExportedTypes.Count() == 0
            && (a.FullName.Contains("netstandard") || a.FullName.Contains("System.Runtime,"))))
        refs.Add(a);

    var options = CSharpParseOptions.Default
        .WithLanguageVersion(LanguageVersion.Latest);

    var compileOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
        .WithAssemblyIdentityComparer(DesktopAssemblyIdentityComparer.Default);

    var compilation = CSharpCompilation.Create("Dynamic",
        new[] { SyntaxFactory.ParseSyntaxTree(source, options) },
        refs.Select(a => a.GetReference()),
        compileOptions
    );

    using var ms = new MemoryStream();
    var e = compilation.Emit(ms);
    if (!e.Success)
        throw new Exception("Compilation failed");
    ms.Seek(0, SeekOrigin.Begin);

    var context = new AssemblyLoadContext(null, true);
    return context.LoadFromStream(ms);
}

// for dynamically implementing some interface;
public static C CompileInstance<C>(string source, IEnumerable<Type> references)
{
    var assembly = Compile(source, references);
    var modelType = assembly.DefinedTypes.Where(t => typeof(C).IsAssignableFrom(t)).Single();

    return (C)Activator.CreateInstance(modelType);
}
  • Related