目录

介绍

代码

代码说明

主程序说明

实用方法

CreateCompilationWithMscorelib(...)方法

EmitToArray(...) 方法

总结


  • GitHub 上的代码

介绍

MS Roslyn是一个很棒的工具,虽然它很新,而且没有很好的文档记录,互联网上的例子很少。我决定写这篇文章是为了填补这个文档/样本空白。

在过去的一年里,我参与了几个项目,这些项目涉及使用MS Roslyn编译器作为服务平台的动态代码生成、编译和创建动态程序集。要创建动态程序集,我会将每个单独的组件分别编译为网络模块,然后将它们组合到程序集(内存DLL)中。采用这种方法是为了避免对尚未修改的组件进行代价高昂的重新编译。

最近花了几个小时试图回答关于为什么将模块加载到动态DLL中会产生错误的github问题。关于为什么尝试加载模块会产生错误的问题,我决定在网上发布解决方案,以便其他人没有以后也要经历同样的痛苦。

代码

该RoslynAssembly解决方案是使用VS 2017创建为一个简单的“控制台应用程序”项目。然后我向其中添加了一个NuGet包Microsoft.CodeAnalysis.CSharp。由于存在对NuGet包的依赖——它会显示它在您的Visual Studio中遗漏了一些引用,但是一旦您编译它(假设您有可用的互联网连接),应该下载并安装NuGet包以及所有引用应该填写。

该代码仅包含单个文件Program.cs 中的一个Program类。

这是示例的代码:

public static class Program
{public static void Main(){try{// code for class Avar classAString = @"public class A {public static string Print() { return ""Hello "";}}";// code for class B (to spice it up, it is a // subclass of A even though it is almost not needed// for the demonstration)var classBString = @"public class B : A{public static string Print(){ return ""World!"";}}";// the main class Program contain static void Main() // that calls A.Print() and B.Print() methodsvar mainProgramString = @"public class Program{public static void Main(){System.Console.Write(A.Print()); System.Console.WriteLine(B.Print());}}";#region class A compilation into A.netmodule// create Roslyn compilation for class Avar compilationA = CreateCompilationWithMscorlib("A", classAString, compilerOptions: new CSharpCompilationOptions(OutputKind.NetModule));// emit the compilation result to a byte array // corresponding to A.netmodule byte codebyte[] compilationAResult = compilationA.EmitToArray();// create a reference to A.netmoduleMetadataReference referenceA = ModuleMetadata.CreateFromImage(compilationAResult).GetReference(display: "A.netmodule");#endregion class A compilation into A.netmodule#region class B compilation into B.netmodule// create Roslyn compilation for class Avar compilationB = CreateCompilationWithMscorlib("B", classBString, compilerOptions: new CSharpCompilationOptions(OutputKind.NetModule), // since class B extends A, we need to // add a reference to A.netmodulereferences: new[] { referenceA });// emit the compilation result to a byte array // corresponding to B.netmodule byte codebyte[] compilationBResult = compilationB.EmitToArray();// create a reference to B.netmoduleMetadataReference referenceB =ModuleMetadata.CreateFromImage(compilationBResult).GetReference(display: "B.netmodule");#endregion class B compilation into B.netmodule#region main program compilation into the assembly// create the Roslyn compilation for the main program with// ConsoleApplication compilation options// adding references to A.netmodule and B.netmodulevar mainCompilation =CreateCompilationWithMscorlib("program", mainProgramString, compilerOptions: new CSharpCompilationOptions(OutputKind.ConsoleApplication), references: new[] { referenceA, referenceB });// Emit the byte result of the compilationbyte[] result = mainCompilation.EmitToArray();// Load the resulting assembly into the domain. Assembly assembly = Assembly.Load(result);#endregion main program compilation into the assembly// load the A.netmodule and B.netmodule into the assembly.assembly.LoadModule("A.netmodule", compilationAResult);assembly.LoadModule("B.netmodule", compilationBResult);#region Test the program// here we get the Program type and // call its static method Main()// to test the program. // It should write "Hello world!"// to the console// get the type Program from the assemblyType programType = assembly.GetType("Program");// Get the static Main() method info from the typeMethodInfo method = programType.GetMethod("Main");// invoke Program.Main() static methodmethod.Invoke(null, null);#endregion Test the program}catch (Exception ex){Console.WriteLine(ex.ToString());}}// a utility method that creates Roslyn compilation// for the passed code. // The compilation references the collection of // passed "references" arguments plus// the mscore library (which is required for the basic// functionality).private static CSharpCompilation CreateCompilationWithMscorlib(string assemblyOrModuleName,string code,CSharpCompilationOptions compilerOptions = null,IEnumerable<MetadataReference> references = null){// create the syntax treeSyntaxTree syntaxTree = SyntaxFactory.ParseSyntaxTree(code, null, "");// get the reference to mscore libraryMetadataReference mscoreLibReference = AssemblyMetadata.CreateFromFile(typeof(string).Assembly.Location).GetReference();// create the allReferences collection consisting of // mscore reference and all the references passed to the methodIEnumerable<MetadataReference> allReferences = new MetadataReference[] { mscoreLibReference };if (references != null){allReferences = allReferences.Concat(references);}// create and return the compilationCSharpCompilation compilation = CSharpCompilation.Create(assemblyOrModuleName,new[] { syntaxTree },options: compilerOptions,references: allReferences);return compilation;}// emit the compilation result into a byte array.// throw an exception with corresponding message// if there are errorsprivate static byte[] EmitToArray(this Compilation compilation){using (var stream = new MemoryStream()){// emit result into a streamvar emitResult = compilation.Emit(stream);if (!emitResult.Success){// if not successful, throw an exceptionDiagnostic firstError =emitResult.Diagnostics.FirstOrDefault(diagnostic =>diagnostic.Severity == DiagnosticSeverity.Error);throw new Exception(firstError?.GetMessage());}// get the byte array from a streamreturn stream.ToArray();}}
}

代码说明

主程序说明

该代码演示了如何编译和组装三个类A,B和Program. A和B编译成网络模块。类Program被编译成可运行的程序集。我们加载A.netmodule和B.netmodule到主组件,然后通过运行测试static方法Program.Main(),其调用这两个类A和B的static方法。

这是A类的代码:

// code for class A
var classAString = @"public class A {public static string Print() { return ""Hello "";}}";  

它的static方法A.Print()打印“Hello ” string。

这是B类代码:

var classBString = @"public class B : A{public static string Print(){ return ""World!"";}}";

它的static方法B.Print()打印string" World!" 请注意,为了增加趣味性,我将 类B继承类 A。这将需要A在B编译期间传递一个引用(如下所示)。

这是Program类的main方法:

var mainProgramString = @"public class Program{public static void Main(){System.Console.Write(A.Print()); System.Console.WriteLine(B.Print());}}";

这是我们如何创建一个A.netmodule和对它的引用。

1、为A.netmodule创建 Roslyn Compilation对象:

var compilationA = CreateCompilationWithMscorlib("A", classAString, compilerOptions: new CSharpCompilationOptions(OutputKind.NetModule));      

方法CreateCompilationWithMscorlib是创建编译的实用方法,将在下面讨论。

2、将编译发送到字节数组:

byte[] compilationAResult = compilationA.EmitToArray();

该数组是模块代码的二进制表示。EmitToArray是另一个效用函数,将在下面详细讨论。

3、创建A.netmodule的引用,用于创建B.netmodule(因为类B取决于类A)以及用于创建主程序代码(因为它也取决于A)。

MetadataReference referenceA = ModuleMetadata.CreateFromImage(compilationAResult).GetReference(display: "A.netmodule");

非常重要的注意事项:每次发出编译结果时(在我们的例子中,它发生在EmitToArray()方法中),结果字节码略有变化,可能是因为时间戳。因此,从相同的Emit结果生成引用和模块代码非常重要。否则,如果模块代码和引用的Emit(...)不同,尝试将模块加载到程序集中将导致哈希不匹配异常,因为用于构建程序集的哈希或引用与模块代码的哈希会有所不同。这是我花了几个小时才弄清楚的原因,这也是我写这篇文章的主要原因。

创建B.netmodule和它的引用几乎和A.netmodule一样,除了我们需要传递一个A.netmodule引用给CreateCompilationWithMscorlib(...)方法(因为类B依赖于类A)。

下面是我们如何创建主程序集:

1、为主程序集创建Roslyn Compilation对象:

var mainCompilation =CreateCompilationWithMscorlib("program", mainProgramString, // note that here we pass the OutputKind set to ConsoleApplicationcompilerOptions: new CSharpCompilationOptions(OutputKind.ConsoleApplication), references: new[] { referenceA, referenceB });
note that we pass <code>OutputKind.ConsoleApplication</code> option since it is an
assembly and not a net module. 

2、将编译结果发送到字节数组中:

byte[] result = mainCompilation.EmitToArray();  

3、将程序集加载到领域中:

Assembly assembly = Assembly.Load(result));  

4、将两个模块加载到程序集中:

assembly.LoadModule("A.netmodule", compilationAResult);
assembly.LoadModule("B.netmodule", compilationBResult);  

注意:在这个阶段,如果引用和模块代码的哈希值不匹配,则会抛出异常。

最后,这里是使用网络模块测试程序集功能的代码:

1、从程序集中获取C#类型Program:

Type programType = assembly.GetType("Program");     

2、从类型为static方法Program.Main()获取MethodInfo:

MethodInfo method = programType.GetMethod("Main");  

3、调用static方法Program.Main():

method.Invoke(null, null); 

这个程序的结果应该是“Hello World!”打印在控制台上。

实用方法

有两种简单的static实用方法:

  • CreateCompilationWithMscorelib(...)——创建roslyn  Compilation
  • EmitToArray(...)——发出一个字节数组,表示编译的.NET代码。

CreateCompilationWithMscorelib(...)方法

该方法的目的是创建一个Roslyn Compilation对象,向其中添加对包含基本.NET功能的mscore库的引用。最重要的是,它还可以添加对作为其最后一个参数“引用”传递的模块的引用(如果需要)。

该方法采用以下参数:

  1. string assemblyOrModuleName——生成的程序集或模块的名称
  2. string code——string包含要编译的代码
  3. CSharpCompilationOptions compilerOptions——应为模块包含new CSharpCompilationOptions(OutputKind.NetModule)或为应用程序包含new CSharpCompilationOptions(OutputKind.ConsoleApplication)
  4. IEnumerable<MetadataReference> references——在引用mscore库之后添加的额外引用

首先,它将代码解析成语法树(Roslyn语法树会转换反映C#语法的string代码对象,为编译做准备):

SyntaxTree syntaxTree = SyntaxFactory.ParseSyntaxTree(code, null, "");  

我们通过连接对mscore库的引用和传递给方法的引用来构建allReferences集合:

// get the reference to mscore library
MetadataReference mscoreLibReference = AssemblyMetadata.CreateFromFile(typeof(string).Assembly.Location).GetReference();// create the allReferences collection consisting of
// mscore reference and all the references passed to the method
IEnumerable allReferences = new MetadataReference[] { mscoreLibReference };
if (references != null)
{allReferences = allReferences.Concat(references);
}  

最后,我们通过CSharpCompilation.Create(...)方法构建并返回 Roslyn Compilation对象:

// create and return the compilation
CSharpCompilation compilation = CSharpCompilation.Create
(assemblyOrModuleName,new[] { syntaxTree },options: compilerOptions,references: allReferences
);return compilation; 

EmitToArray(...) 方法

EmitToArray(...)方法的目的是发出字节码(真正的编译实际上发生在这个阶段),检查错误(如果发出不成功则抛出异常)并返回.NET代码的字节数组。

它只需要一个参数——Roslyn Compilation类型的“编译” 。

首先,我们创建MemoryStream以容纳字节数组。然后,我们将编译结果发送到stream:

using (var stream = new MemoryStream())
{// emit result into a streamvar emitResult = compilation.Emit(stream);  

然后,我们测试编译结果是否有错误并抛出包含第一条错误消息的异常(如果发现错误):

if (!emitResult.Success)
{// if not successful, throw an exceptionDiagnostic firstError =emitResult.Diagnostics.FirstOrDefault(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error);throw new Exception(firstError?.GetMessage());
} 

最后(如果没有错误),我们从stream返回字节数组:

return stream.ToArray();  

总结

Roslyn是一个非常强大且未充分利用的框架,由于缺乏文档和示例,大多数公司并未完全实现其全部功能。

在本文中,我将解释如何使用Roslyn在运行时将动态生成的代码编译和组装成可执行的动态程序集。

我尝试解释编译和组装的每个阶段,并详细提到可能的陷阱,这样本文的读者就不必像我一样花太多时间试图使事情正常工作。

https://www.codeproject.com/Articles/1215168/Using-Roslyn-for-Compiling-Code-into-Separate-Net、

使用Roslyn将代码编译成单独的网络模块并将它们组装成动态库相关推荐

  1. VS2017 编译 PDFium 源码,生成 x86、x64,动态库、静态库

    PDFium 源码编译 一.下载源码:         1.下载 depot_tools 源码:                https://chromium.googlesource.com/ch ...

  2. 执行引擎的工作过程、Java代码编译和执行的过程、解释器、JIT编译器

    执行引擎概述 执行引擎是Java虛拟机核心的组成部分之一. "虚拟机"是-一个相对于"物理机"的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接 ...

  3. 计算机科学基础知识(四): 动态库和位置无关代码

    一.前言 本文主要描述了动态库以及和动态库有紧密联系的位置无关代码的相关资讯.首先介绍了动态库和位置无关代码的源由,了解这些背景知识有助于理解和学习动态库.随后,我们通过加-fPIC和不加这个编译选项 ...

  4. go语言动态库的编译和使用

    本文主要介绍go语言动态库的编译和使用方法,以linux平台为例,windows平台步骤一样,具体环境如下: $ echo $GOPATH /media/sf_share/git/go_practic ...

  5. Linux下静态库和动态库的编译连接

    http://blog.sina.com.cn/s/blog_4090ba590100t3nu.html .a文件 gcc -c test.c  ar rc libtest.a test.o  ran ...

  6. 编写简单的连接MongoDB数据库C++程序 解决编译C++程序时链接MongoDB动态库失败的问题...

    一. 安装好mongo数据库以后,创建一个用来链接数据库的简单C++程序mon2.cpp,发现很多网站都用这个程序做示例. 不过重点在于如何让这个程序真正可以跑起来显示出来结果,着实费了一番功夫. 1 ...

  7. 在ubuntu下使用cmake进行opencv的配置和Windows下进行使用cmake编译源代码比较,opencv3进行g++例子程序编译、动态库的制作

    1.首先安装的是cmake软件,使用指令: apt-get install cmake 接着查看版本,测试是否安装成功: root@emouse:/home# cmake --version cmak ...

  8. msvc命令行编译静态库和动态库

    编写一个静态库 编写要打包为静态库的函数,内容如下: // jclib.cpp int func(int a, int b) {return a + b; } 在msvc开发人员命令提示符中执行 cl ...

  9. Linux:编译动态库时遇到的错误relocation R_X86_64_32 against `a local symbol'

    编译动态库时遇到如下错误: ... ... relocation R_X86_64_32 against `a local symbol' can not be used when making a ...

最新文章

  1. Open vSwitch 安装
  2. [2dPIC调试笔记]输入参数归一化1014(1)
  3. 对象 普通po转_谈谈C++对象的构造
  4. JS中的this好神奇,都把我弄晕了
  5. Flash背景透明的代码
  6. Python: 50个能够满足所有需要的模块
  7. 换手机的再等等!iPhone SE2还有戏:苹果官网悄然更新AppleCare+服务计划
  8. 小女出世,暂停工作,全职照料大人小孩
  9. 一个页面可以重复调用的TAB选项卡切换js代码 鼠标悬浮
  10. DB2 sql报错后查证原因与解决问题的方法
  11. 小米电视4A核心技术之语音识别浅析
  12. Python 算法交易实验23 退而结网1
  13. 任玉刚【Android开发艺术探索】读后笔记二
  14. Java数组、集合、散列表常见算法浅析
  15. 20190628 《此生,未完成》-- 于娟
  16. 计算机网络-读书笔记
  17. PHP实现文件下载功能,提示压缩包损坏及打不开的解决方法
  18. 多相位图像插值算法(Lanczos、sinc)
  19. 【分块】【Violet】蒲公英
  20. gcc posix sjij for MSYS 9.2.1+

热门文章

  1. mysql 64位整型_高性能MySQL笔记精简(整数和实数优化)
  2. 还服务器网站被k,导致网站被K的主要原因,看看你有没有中招!
  3. idea package自动生成_IDEA -- 自动创建POJO
  4. 开源网格划分软件_网格划分:PointWise 18.3R1
  5. python json loads 中文乱码_python实现智能语音天气预报
  6. 甜蜜暴击情人节海报PSD分层模板|让人眼前一亮
  7. 彩色烟雾一直是许多摄影师和摄影爱好者的首选武器
  8. 阅读类app界面设计UI可临摹素材模板
  9. python语言中最基本的概念_Python 学习之路-基本的概念(三)
  10. c++ assert用法