使用 .net core 3.0 动态编译和运行代码进行脚本编写 [英] Dynamically compile and run code using .net core 3.0 for scripting

查看:29
本文介绍了使用 .net core 3.0 动态编译和运行代码进行脚本编写的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想提供在 .NET core 3 中编译和运行代码(Csharp 类)的可能性,以便编写脚本.脚本(类)应从文件系统加载并注入现有(静态)程序集中.https://laurentkempe.com/2019/02/18/dynamically-compile-and-run-code-using-dotNET-Core-3.0/(使用 AssemblyContext)似乎是一种有效的方法.

I want to offer the possibly to compile and run code (Csharp Classes) in .NET core 3 for purpose of scripting. The scripts (classes) shall be loaded from the file system and injected in an existing (static) assembly. https://laurentkempe.com/2019/02/18/dynamically-compile-and-run-code-using-dotNET-Core-3.0/ (using AssemblyContext) seems to be a valid approach for this.

如果我不需要在程序集中隔离脚本代码,是否有更简单的解决方案(开销更少)?.(调试应该是可以的)

Is there a simpler solution (with less overhead) if I do not have the need to isolate the script code in an assembly?. (Debugging should be possible)

推荐答案

https://laurentkempe.com/2019/02/18/dynamically-compile-and-run-code-using-dotNET-Core-3.0/.

为了节省时间,这里有一个运行单个文件、支持 LINQ 和来自当前项目 DLL 的类的程序的变体:

To save time, here is a variant of the program that runs a single file, supports LINQ, and classes from current project DLL :

程序.cs

using System.Linq;
using DynamicRun.Builder;

namespace DynamicRun
{
  class Program
  {
    static void Main(string[] args)
    {
      var compiler = new Compiler();
      var runner = new Runner();

      byte[] compiled = compiler.Compile(args.FirstOrDefault());
      runner.Execute(compiled, args[1..args.Length]);
    }
  }
}

编译器.cs

using System;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;

namespace DynamicRun.Builder
{
    internal class Compiler
    {
        public byte[] Compile(string filepath)
        {
            var sourceCode = File.ReadAllText(filepath);

            using (var peStream = new MemoryStream())
            {
                var result = GenerateCode(sourceCode).Emit(peStream);

                if (!result.Success)
                {
                    Console.WriteLine("Compilation done with error.");

                    var failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error);

                    foreach (var diagnostic in failures)
                    {
                        Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
                    }

                    return null;
                }

                Console.WriteLine("Compilation done without any error.");

                peStream.Seek(0, SeekOrigin.Begin);

                return peStream.ToArray();
            }
        }

        private static CSharpCompilation GenerateCode(string sourceCode)
        {
            var codeString = SourceText.From(sourceCode);
            var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3);

            var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options);

            var references = new MetadataReference[]
            {
                MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
                MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
                MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location),
                MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location),
                MetadataReference.CreateFromFile(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location),
                // Todo : to load current dll in the compilation context
                MetadataReference.CreateFromFile(typeof(Family.Startup).Assembly.Location),
            };

            return CSharpCompilation.Create("Hello.dll",
                new[] { parsedSyntaxTree }, 
                references: references, 
                options: new CSharpCompilationOptions(OutputKind.ConsoleApplication, 
                    optimizationLevel: OptimizationLevel.Release,
                    assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default));
        }
    }
}

Runner.cs

using System;
using System.IO;
using System.Runtime.CompilerServices;

namespace DynamicRun.Builder
{
  internal class Runner
  {
    public void Execute(byte[] compiledAssembly, string[] args)
    {
      var assemblyLoadContext = LoadAndExecute(compiledAssembly, args);

      for (var i = 0; i < 8 && assemblyLoadContext.IsAlive; i++)
      {
        GC.Collect();
        GC.WaitForPendingFinalizers();
      }

      Console.WriteLine(assemblyLoadContext.IsAlive ? "Unloading failed!" : "Unloading success!");
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static WeakReference LoadAndExecute(byte[] compiledAssembly, string[] args)
    {
      using (var asm = new MemoryStream(compiledAssembly))
      {
        var assemblyLoadContext = new SimpleUnloadableAssemblyLoadContext();

        var assembly = assemblyLoadContext.LoadFromStream(asm);

        var entry = assembly.EntryPoint;

        _ = entry != null && entry.GetParameters().Length > 0
                ? entry.Invoke(null, new object[] {args})
                : entry.Invoke(null, null);

        assemblyLoadContext.Unload();

        return new WeakReference(assemblyLoadContext);
      }
    }
  }
}

这篇关于使用 .net core 3.0 动态编译和运行代码进行脚本编写的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆