.NET Core/.NET Framework 的 System.Reflection.Emit 命名空间为我们提供了动态生成 IL 代码的能力。利用这项能力,我们能够在运行时生成一段代码/一个方法/一个类/一个程序集。

本文将介绍使用 Emit 生成 IL 代码的方法,以及在此过程中可能遇到的各种问题。


在编写以下代码时如果遇到一些意料之外的错误,希望调试生成的 IL 代码,可以尝试阅读 如何快速编写和调试 Emit 生成 IL 的代码 - walterlv 了解如何调试和解决。

 

用 Emit 生成 IL 代码时,很多我们写 C# 时不会注意到的问题现在都需要开始留意。

在阅读本文之前,希望统一一个平时可能不太留意的英文:

  • 形参:parameter
  • 实参:argument

如果不了解它们之间的区别,请自行搜索。

定义方法签名

在 IL 中,方法名称可以使用比 C# 更多的字符,例如“<”和“>”,这也是 C# 编译闭包时喜欢使用的字符。目前我还没有找到 IL 中哪些字符可以作为标识符名称,但从混淆工具来看,是比 C# 多得多的。

如果你试图生成实例方法,那么实例本身 this 将成为第一个参数,不过并不需要额外将它定义到参数列表中。

当然,如果是静态方法,我们能够自己指定一个 this 参数,不过没有实际的意义。

var method = new DynamicMethod("<MethodName>",
typeof(void), new[] { typeof(object), typeof(object) });
method.DefineParameter(1, ParameterAttributes.None, "this");
method.DefineParameter(2, ParameterAttributes.None, "value");

如果不声明形参,那么生成的 IL 代码的函数将无法被正常调用(提示可能造成运行时的不稳定)。

声明和初始化局部变量

平时写 C# 的时候,可能一个方法里面没有定义任何一个局部变量,但 IL 可不一定这么认为。

例如:

int a = 0;
if (value.GetType() == typeof(string))
{
}
else
{
}

实际上,在 IL 中,除了 Int32 类型的 a 之外,还会额外定义一个 bool 类型的局部变量 V_1。在 value.GetType() == typeof(string) 执行完后,其值将存入 V_1

所以,如果需要编写 Emit 生成代码的代码,需要注意这些隐式产生的局部变量,它们需要和普通变量一样被初始化。

Emit 代码为:

// 这就声明了两个局部变量。
il.DeclareLocal(typeof(int));
il.DeclareLocal(typeof(bool));

定义标签

如果代码中存在非线性结构,例如 if-else,那么 IL 就需要知道跳转的地址。那么如何能够知道跳转到哪个地址呢?

——使用标签

if-else 来说,if 操作需要知道 else 的起始地址;对于 if 内部的结尾来说,需要知道整个 if-else 结束之后的第一个操作的地址。

var startOfElse = il.DefineLabel();
var endOfWholeIfElse = il.DefineLabel(); il.Emit(OpCodes.Nop);
// 其他生成代码。
// 如果 if 条件不满足,跳转到 startOfElse。
il.Emit(OpCodes.Brfalse_S, startOfElse);
// 其他生成代码。
// 在 if 结束之后,跳转到 endOfWholeIfElse 地址。
il.Emit(OpCodes.Br_S, endOfWholeIfElse);
// 其他生成代码。
// 假设这里到了 else 的开头了,于是将 startOfElse 进行标记。标记完紧跟着写 else 部分的代码。
il.MarkLabel(startOfElse);
il.Emit(OpCodes.Nop);
// 其他生成代码。
// 假设这里整个 if-else 结束了,于是将 endOfWholeIfElse 进行标记。
il.MarkLabel(endOfWholeIfElse);

参考资料

最新文章

  1. JVM性能调优监控工具jps、jstack、jmap、jhat、jstat、hprof使用详解
  2. Django笔记-字符编码相关问题整理
  3. C++ UFunction({FLAG}) 宏 FLAG 解释笔记
  4. Asp.Net Web API 2第九课——自承载Web API
  5. 使用node.js制作简易爬虫
  6. git pull使用【转】
  7. Javascript之图片上传预览
  8. python下载多个文件
  9. [.net 面向对象程序设计深入](14)Redis——基础
  10. kotlin 语言入门指南(三)--编码习惯
  11. AOP - 2 实例(SpringBoot 注解方式)
  12. VS 2017 + opencv4.0
  13. 200 行代码使用 C# 实现区块链
  14. axios跨域请求报错
  15. sql server中的工作线程
  16. wxWidgets:动态EVENT绑定
  17. Ubuntu16.04安装使用Consul
  18. css如何画出类似原生的线条?
  19. Linux的硬链接和软链接
  20. [DeeplearningAI笔记]卷积神经网络1.2-1.3边缘检测

热门文章

  1. arm-linux-gcc安装使用教程
  2. Matlab 实现对码功能
  3. centos cgroup配置
  4. SPA(单页面web应用程序)
  5. C++名字查找和重载
  6. Angular2 直接给元素指定超链接打开会带有 `unsafe` 字样导致数据不能加载
  7. java, double转String, 去掉0结尾的小数位
  8. JavaScript封装Ajax工具函数及jQuery中的ajax,xhr在IE的兼容
  9. C++(二十四) — 指向字符的指针为什么可以用字符串来初始化,而不是字符地址?
  10. 第九天 1-8 RHEL7软件包管理