一、System.Reflection.Emit概述

  Emit,可以称为发出或者产生。与Emit相关的类基本都存在于System.Reflection.Emit命名空间下。反射,我们可以取得形如程序集包含哪些类型,类型包含哪些方法等等大量的信息,而Emit则可以在运行时动态生成代码。

二、IL代码解析

     以下代码为例:

          static void Main(string[] args)
{
int i = ;
int j = ;
int k = ;
Console.WriteLine(i+j+k);
}

翻译文IL代码为:

    .method private hidebysig static void  Main(string[] args) cil managed
{
.entrypoint //程序入口
// Code size 19 (0x13)
.maxstack //定义函数代码所用堆栈的最大深度,也可理解为Call Stack的变量个数 //以下我们把它看做是完成代码中的初始化
.locals init (int32 V_0,int32 V_1,int32 V_2) //定义 int 类型参数 V_0,V_1,V_2 (此时已经把V_0,V_1,V_2存入了Call Stack中)
IL_0000: nop //即No Operation 没有任何操作,我们也不用管它 IL_0001: ldc.i4. //加载第一个变量"i"      (压入Evaluation Stack中)
IL_0002: stloc. //把"i"赋值给Call Stack中第0个位置(V_0)   
IL_0003: ldc.i4. //加载第二个变量"j"       (压入Evaluation Stack中) 
IL_0004: stloc. //把"j"赋值给Call Stack中第1个位置(V_1)
IL_0005: ldc.i4. //加载第三个变量"k"       (压入Evaluation Stack中)
IL_0006: stloc. //把 "k" 赋值给Call Stack中第2个位置(V_2) //上面代码初始化完成后要开始输出了,所要把数据从Call Stack中取出 IL_0007: ldloc. //取Call Stack中位置为0的元素(V_0)的值("i"的值)  (相当于Copy一份值Call Stack中V_0的值。V_0本身的值是不变的)
IL_0008: ldloc. //取Call Stack中位置为1的元素(V_1)的值("j"的值)     (同上)
IL_0009: add // 做加法操作
IL_000a: ldloc. // 取出Call Stack中位置为2的元素(V_2)的值("k"的值)
IL_000b: add // 做加法操作
IL_000c: call void [mscorlib]System.Console::WriteLine(int32) //调用输出方法
IL_0011: nop
IL_0012: ret //即为 return 标记 返回值
} // end of method Program::Main

指令详解
Managed Heap::这是动态配置(Dynamic Allocation)的记忆体,由 Garbage Collector(GC)在执行时自动管理,整个 Process 共用一个Managed Heap(我理解为托管堆,存储引用类型的值)。
Evaluation Stack:这是由 .NET CLR 在执行时自动管理的记忆体,每个 Thread 都有自己专属的 Evaluation Stack(我理解为类似一个临时存放值类型数据的线程栈)
Call Stack:这个是由 .NET CLR 在执行时自动管理的记忆体,每个 Thread 都有自己专属的 Call Stack。每呼叫一次 method,就会使得 Call Stack 上多了一個个 Record Frame;呼叫完成之後,此 Record Frame 会被丢弃(我理解为一个局部变量表,用于存放.locals init(int32 V_0)指令的参数值如:V_0)

.maxstack:代码中变量需要在Call Stack 中占用几个位置
.locals init (int32 V_0,int32 V_1,int32 V_2):定义变量并存入Call Stack中
nop:即No Operation 没有任何操作,我们也不用管它,
ldstr.:即Load String 把字符串加压入Evaluation Stack中
stloc.:把Evaluation Stack中的值弹出赋值到Call Stack中
ldloc.:把Call Stack中指定位置的值取出(copy)存入 Evaluation Stack中 以上两条指令为相互的操作stloc赋值,ldloc取值
call: 调用指定的方法
ret: 即return 标记返回

二、动态生成代码

  首先我们需要了解每个动态类型在.net中都是用什么类型来表示的。

程序集:System.Reflection.Emit.AssemblyBuilder(定义并表示动态程序集)

构造函数:System.Reflection.Emit.ConstructorBuilder(定义并表示动态类的构造函数)

自定义属性:System.Reflection.Emit.CustomAttributeBuilder(帮助生成自定义属性 使用构造函数传递的参数来生成类的属性)

枚举:System.Reflection.Emit.EnumBuilder(说明并表示枚举类型)

事件:System.Reflection.Emit.EventBuilder(定义类的事件)

字段:System.Reflection.Emit.FieldBuilder(定义并表示字段。无法继承此类)

局部变量:System.Reflection.Emit.LocalBuilder(表示方法或构造函数内的局部变量)

方法:System.Reflection.Emit.MethodBuilder(定义并表示动态类的方法(或构造函数))

模块:System.Reflection.Emit.ModuleBuilder(定义和表示动态程序集中的模块)

参数:System.Reflection.Emit.ParameterBuilder(创建或关联参数信息 如:方法参数,事件参数等)

属性:System.Reflection.Emit.PropertyBuilder(定义类型的属性 (Property))

类:System.Reflection.Emit.TypeBuilder(在运行时定义并创建类的新实例)

   以下介绍Emit生成代码的基本流程:

  1.构建程序集

  在创建程序集之前,我们先要为它取个名字。

var asmName = new AssemblyName("Test");

AssemblyName位于System.Reflection命名空间下,它代表程序集的名称。

  然后我们就可以用上面的名字来创建一个程序集了:

var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.RunAndSave);

AssemblyBuilderAccess.ReflectionOnly:  
DefineDynamicAssembly有很多重载,比如上面的例子可以添加第三个参数用于作为生成的程序集要存放到的目录。关于其他重载形式,大家可以查阅MSDN。这里重点说说AssemblyBuilderAccess这个枚举。  
它有以下几个值:  
AssemblyBuilderAccess.ReflectionOnly:表示动态程序集只能用于反射获取元素据用,不能执行。  
AssemblyBuilderAccess.Run:表示动态程序集是用于执行的。  
AssemblyBuilderAccess.Save:表示动态程序集会被保存到磁盘上,不能立即执行。 
AssemblyBuilderAccess.RunAndSave:表示动态程序集会被保存至磁盘并能立即执行。

  2.创建模块

  创建程序集后,就需要为程序集添加模块了,我们可以如下定义一个模块:

var mdlBldr = asmBuilder.DefineDynamicModule("Main", "Main.dll");

如果想把动态生成的程序集保存至磁盘(如本例),定义模块时模块所在文件的名称一定要和保存程序集(后面会提到)时提供的文件名称一样。

  3.定义类

  有了前面的准备工作,我们开始定义我们的类型:

var typeBldr = mdlBldr.DefineType("Hello",TypeAttributes.Public);

DefineType还可以设置要定义的类的基类,要实现的接口等等。

  4.定义类成员(方法,属性等等)

  既然有了类,下面我们就为它添加一个SayHello方法吧:

 var methodBldr = typeBldr.DefineMethod(
"SayHello",
MethodAttributes.Public,
null,//return type
null//parameter type
);

该方法的原型为public void SayHell();

  方法签名已经生成好了,但方法还缺少实现。在生成方法的实现前,必须提及一个很重要的概念:evaluation stack。在.Net下基本所有的操作都是通过入栈出栈完成的。这个栈就是evaluation stack。比如要计算两个数(a,b)的和,首先要将a放入evaluation stack中,然后再将b也放入栈中,最后执行加法时将弹出栈顶的两个元素也就是a和b,相加再将结果推送至栈顶。

  Console.WriteLine("Hello,World")可以用Emit这样生成:

 var il = methodBldr.GetILGenerator();//获取il生成器

 il.Emit(OpCodes.Ldstr,"Hello, World");

 il.Emit(OpCodes.Call,typeof(Console).GetMethod("WriteLine",new Type[]{typeof(string)}));

 il.Emit(OpCodes.Ret);

OpCodes枚举定义了所有可能的操作,这里用到了:

  ldStr:加载一个字符串到evaluation stack。

  Call:调用方法。

  Ret:返回,当evaluation stack有值时会返回栈顶值。

  完成上面的步骤,一个类型好像就已经完成了。事实上却还没有,最后我们还必须显示的调用CreateType来完成类型的创建。

typeBldr.CreateType();

这样一个完整的类就算完成了。但为了能用reflector查看我们创建的动态程序集,我们选择将这个程序集保存下来。

asmBuilder.Save("Main.dll");

如前面定义模块时所说,这里文件名字必须和模块保存到的文件一致,否则我们前面定义的模块和这个模块的一切就无家可归了。接下来,(如果在定义模块时未指定动态创建的程序要保存到哪个目录)我们就可以到 Debug目录下看看生成的Main.dll了,用Reflector打开可以看到:

  三、不包含main的控制台程序

  一直以来,应用程序(控制台,winform)都是从Main函数启动的,如果没有Main还能启动吗?答案是可以,下面就用emit来做这样一个控制台程序,完整代码如下:

 var asmName = new AssemblyName("Test");   

             var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(

                 asmName,

                 AssemblyBuilderAccess.RunAndSave);  

             var mdlBldr = asmBuilder.DefineDynamicModule("Main", "Main.exe");  

             var typeBldr = mdlBldr.DefineType("Hello", TypeAttributes.Public);  

             var methodBldr = typeBldr.DefineMethod(

                 "SayHello",         

                 MethodAttributes.Public | MethodAttributes.Static,     

                 null,//return type               

                 null//parameter type       

                 );            

             var il = methodBldr.GetILGenerator();//获取il生成器  

             il.Emit(OpCodes.Ldstr,"Hello, World");    

             il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[]{typeof(string)})); 

             il.Emit(OpCodes.Call,  typeof(Console).GetMethod("ReadLine"));   

             il.Emit(OpCodes.Pop);//读入的值会被推送至evaluation stack,而本方法是没有返回值的,因此,需要将栈上的值抛弃    

             il.Emit(OpCodes.Ret);                     

             var t = typeBldr.CreateType();          

             asmBuilder.SetEntryPoint(t.GetMethod("SayHello"));    

             asmBuilder.Save("Main.exe");

C#反射发出System.Reflection.Emit学习的更多相关文章

  1. System.Reflection.Emit学习

    C#反射发出System.Reflection.Emit学习 分享: 1 一.System.Reflection.Emit概述 Emit,可以称为发出或者产生.与Emit相关的类基本都存在于Syste ...

  2. System.Reflection.Emit摘记

    动态类型在.net中都是用什么类型来表示的.程序集:System.Reflection.Emit.AssemblyBuilder(定义并表示动态程序集)构造函数:System.Reflection.E ...

  3. [EF] - 动态创建模型:System.Reflection.Emit + Code First

    动态创建Entity Framework模型并且创建数据库 使用System.Reflection.Emit+Code First model创建以下的一个实体类和DbContext并且创建数据库: ...

  4. 基础命名空间:反射 using System.Reflection

    反射概念:       .Net的应用程序由几个部分:‘程序集(Assembly)’.‘模块(Module)’.‘类型(class)’组成,程序集包含模块 模块包含类型,类型又包含 成员,而反射提供一 ...

  5. 反射基础 System.Reflection

    一.获取程序集Assembly 1.获取当前运行的程序集 System.Reflection.Assembly[] asm = AppDomain.CurrentDomain.GetAssemblie ...

  6. [读行者][学习LinqExpression和Reflection(Emit)]阅读TypeBuilderSample之ExampleFromTheArticle

    前言 关于”读行者“ 俗语有云:"读万卷书,行万里路“.多读一些优秀代码,不仅可以锻炼我们读代码的能力(便于维护或相互交流),还可以吸取很多我们成长所需的知识点.多读,才能开阔我们的眼界,才 ...

  7. Emit学习(1)-Emit概览

    一.Emit概述 Emit,可以称为发出或者产生.在Framework中,与Emit相关的类基本都存在于System.Reflection.Emit命名空间下.可见Emit是作为反射的一个元素存在的. ...

  8. 【C#基础】System.Reflection (反射)

    在使用.NET创建的程序或组件时,元数据(metadata)和代码(code)都存储于"自成一体"的单元中,这个单元称为装配件.我们可以在程序运行期间访问这些信息.在System. ...

  9. 添加一种emit的应用,反射发出,较直接调用稍慢,但好过反射与表达式树。

    System.Reflection.MethodInfo mInfo = typeof(TypeParse).GetMethod("Add", System.Reflection. ...

随机推荐

  1. JS简单路由实现

    说一下前端路由实现的简要原理,以 hash 形式(也可以使用 History API 来处理)为例, 当 url 的 hash 发生变化时,触发 hashchange 注册的回调,回调中去进行不同的操 ...

  2. 对SNL语言的解释器实现尾递归优化

    对于SNL语言解释器的内容可以参考我的前一篇文章<使用antlr4及java实现snl语言的解释器>.此文只讲一下"尾递归优化"是如何实现的--"尾递归优化& ...

  3. Xilinx FPGA编程技巧之常用时序约束详解

    1.   基本的约束方法 为了保证成功的设计,所有路径的时序要求必须能够让执行工具获取.最普遍的三种路径为: 输入路径(Input Path),使用输入约束 寄存器到寄存器路径(Register-to ...

  4. canvas一周一练 -- canvas绘制饼图(3)

    运行效果: <!DOCTYPE html> <html> <head> </head> <body> <canvas id=" ...

  5. HDU_1175_连连看

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=1175 大意:连连看规则,只能转两次弯,先输入矩阵0表示没有棋子,正整数表示不同的棋子,然后询问,输入两点坐 ...

  6. swift 扩展 要素总结

    类: 协议: 泛型及元素类型:扩展约束:

  7. sql的for update

    欢迎大家吐槽 oracle行级共享锁 通常是通过select … from for update语句添加的,同时该方法也是我们用来手工锁定某些记录的主要方法.比如,当我们在查询某些记录的过程中,不希望 ...

  8. JAVA程序员面试笔试宝典4

    1.HTTP中GET与POST方法有什么区别? GET方法上传数据时,数据添加在URL后面.同时,数据大小有限制,通常在1024Byte左右.POST方法传递数据是通过HTTP请求的附件进行的,传递的 ...

  9. Spring框架系列(七)--Spring常用注解

    Spring部分: 1.声明bean的注解: @Component:组件,没有明确的角色 @Service:在业务逻辑层使用(service层) @Repository:在数据访问层使用(dao层) ...

  10. Microsoft SQL Server 存储过程

    Microsoft SQL Server 存储过程 TRIGGER DDL触发器:主要用于防止对数据库架构.视图.表.存储过程等进行的某些修改:DDL事件是指对数据库CREATE,ALTER,DROP ...