emit IL代码介绍
前段时间使用了Reflection.Emit运行时生成代码,学习了一些IL指令,在此留点笔迹,以方便记忆。
在介绍IL代码前,先介绍下自带的反编译工具-ildasm.exe。这是一个学习IL指令很好用的工具,在写IL指令遇到问题时,可以先用C#完成你想表达的代码,编译成程序集(*.dll)或可执行文件(*.exe),然后用ildasm.exe查看其中的指令,模仿它们来完成你的工作。这也是我常用的方法^_^。
如下C#代码:
public static int Add(int num1, int num2)
{
int sum, exNum;
sum = num1 + num2;
exNum = 5;
sum = sum + exNum;
return sum;
}
用ildasm.exe查看,如下:
.method public hidebysig static int32 Add(int32 num1,int32 num2) cil managed
{
// 代码大小 17 (0x11)
.maxstack 2
.locals init ([0] int32 sum,[1] int32 exNum,[2] int32 CS$1$0000)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.1
IL_0003: add
IL_0004: stloc.0
IL_0005: ldc.i4.5
IL_0006: stloc.1
IL_0007: ldloc.0
IL_0008: ldloc.1
IL_0009: add
IL_000a: stloc.0
IL_000b: ldloc.0
IL_000c: stloc.2
IL_000d: br.s IL_000f
IL_000f: ldloc.2
IL_0010: ret
} // end of method TestClass::Add
用ILGenerator.Emit代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static void Generator1()
{
AssemblyName asmName = new AssemblyName("DynamicAssembly");
AssemblyBuilder asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
asmName, AssemblyBuilderAccess.Run);
ModuleBuilder moudleBuilder = asmBuilder.DefineDynamicModule("DynamicModule");
TypeBuilder typeBuilder = moudleBuilder.DefineType("Class1", TypeAttributes.Public);
// 定义方法
MethodBuilder methodBuilder = typeBuilder.DefineMethod("Add",
MethodAttributes.Static | MethodAttributes.Public,
CallingConventions.Standard, typeof(int), new Type[] { typeof(int), typeof(int) });
// 获取IL生成器
ILGenerator generator = methodBuilder.GetILGenerator();
generator.DeclareLocal(typeof(int)); //声明变量
generator.DeclareLocal(typeof(int)); //声明变量
//加载参数1,需要注意的是静态方法Ldarg_0对应的是参数1,
//实例方法Ldarg_0对应的是this指针,Ldarg_1对应的是参数1,
//即每个实例方法都传递一个隐含的this指针
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1); //加载参数2到内存
generator.Emit(OpCodes.Add); //pop参数1+pop参数2,并push(结果)
generator.Emit(OpCodes.Stloc_0); //pop元素,并赋值于变量1
generator.Emit(OpCodes.Ldc_I4, 5);
generator.Emit(OpCodes.Stloc_1);
generator.Emit(OpCodes.Ldloc_0);
generator.Emit(OpCodes.Ldloc_1);
generator.Emit(OpCodes.Add);
//generator.Emit(OpCodes.Ldloc_1); //如果不注释的话,调用会出现异常,知道原因吗?
generator.Emit(OpCodes.Ret); //返回
Type type = typeBuilder.CreateType();
MethodInfo method = type.GetMethod("Add");
Console.WriteLine(method.Invoke(null, new object[] { 5, 10 }));
}
上面是个很简单的例子,遇到复杂的情况可以通过ildasm.exe的帮助来完成,每个具体的IL指令介绍觉得没有必要,想了解更多的IL指令的话可以参考MSDN的MSIL指令集。
不知道有没有注意到上面例子中注释掉的那条语句,为什么取消注释的话,程序调用就会出现异常?
IL是完全基于栈模型的,没有使用任何的寄存器(在编译成机器语言优化的时候会把常用的参数或局部变量放入寄存器中),这是基于通用性的考虑。通常情况下,栈向下(低地址)增长,每向栈中push一个元素,栈顶就向低地址扩展,每向栈中pop一个元素,栈顶就向高地址回退。一个函数内的局部变量以及其调用下一级函数的参数,所占用的内存空间作为一个基本单元,成为帧(frame)。下面是栈的基本模型图
之所以发生异常是因为函数退出时,栈内有多余的数据,没能回退到栈顶,再加一条generator.Emit(OpCodes.Pop)指令即可运行正常。
再看下面得语句,会生成什么样的IL代码?
generator.Emit(OpCodes.Ldc_I4_S, 1);
通过ildasm.exe可以看到代码如下:
IL_0011: ldc.i4.s 1
IL_0013: nop
IL_0014: nop
IL_0015: nop
很多人会疑问3条nop指令从何而来?这主要是:所有的IL指令都被简单的序列化成字节流,指令间没有分隔符,根据每个IL指令要操作的字节数来完成分割。多字节操作数将被序列化成小端顺序存储(低位在前,高位在后),而ldc_I4_S指令要求后面的操作数是1字节(8bit),而1被存储成01000000(小端顺序),幸运的是00在IL指令中对应的是nop指令,即不进行任何操作。如1换成200就会出现异常,需要多注意指令操作数的字节数。