一、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. jQuery中$this和$(this)的区别

    要写一个点击弹窗任意地方,关闭弹窗.点击事件写标签在元素上 onclick =  closepop(this),这时候很容易搞不清楚怎么去获取当前元素 function closepop(e){ va ...

  2. springboot运行模式

    1.springboot项目常见的运行方式:  2.说明: idea:在开发环境中跑项目,也就是我们在编码过程中的用的做多的方式 jar.war:线上.服务器上执行jar.war包的方式 maven插 ...

  3. React Native组件的结构和生命周期

    React Native组件的结构和生命周期 一.组件的结构 1.导入引用 可以理解为C++编程中的头文件. 导入引用包括导入react native定义的组件.API,以及自定义的组件. 1.1 导 ...

  4. C/c++中 数组全局变量的定义声明 与 引用声明

    数组声明一次,所有的  定义声明  有切仅有一次! 别人遇到的问题如下: 在声明定义时,定义数组如下: int G_glob[100]; 在其他文件引用时声明如下: int *G_glob; 这样的操 ...

  5. HDU_1242_Rescue

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=1242 大意:迷宫搜索,'#'代表墙,'.'代表路,'x'代表守卫,每移动1格用1秒,杀死守卫用1秒,ang ...

  6. solr 6.5.1 linux 环境安装

    前言 最近在研究搜索引擎,准备搭建一套属于自己的搜索APP,用于搜索的数据我已通过scrapy抓到本地了,现在需要一个搜索引擎来跑这些数据.于是选择了基于Lucene的solr来为我做搜索引擎的工作. ...

  7. java.lang.NoClassDefFoundError: org/hibernate/validator/internal/engine/DefaultClockProvider

    ①在springboot的spring-boot-starter-web默认引入了以下依赖: <dependency> <groupId>com.fasterxml.jacks ...

  8. CAD梦想看图6.0安卓版 20181022更新

    下载地址: http://www.mxdraw.com/ndetail_10109.html 1. 保存上次的文件浏览位置和绘制颜色 2. 调整工具条按钮位置和文字 3. 增加测量距离和面积时的捕捉功 ...

  9. Tensorflow学习笔记(1):tf.slice()函数使用

    tensorflow 当中的一个常用函数:Slice() def slice(input_, begin, size, name=None) 函数的功能是根据begin和size指定获取input的部 ...

  10. 最大子段和(洛谷P1115,动态规划递推)

    洛谷题目链接 题目赋值出来格式有问题,所以我就只放题目链接了 下面为ac代码 #include<bits/stdc++.h> #define ll long long using name ...