`

Emit学习之旅(1):Emit概览

    博客分类:
  • C#
阅读更多

转载Frog 博客空间:http://www.cnblogs.com/MythYsJh/archive/2010/04/19/1715383.html

 

一、Emit概述

  Emit,可以称为发出或者产生。在Framework中,与Emit相关的类基本都存在于System.Reflection.Emit命名 空间下。可见Emit是作为反射的一个元素存在的。说道反射,大家应该都不陌生,它允许我们查看程序集的元素据,从而取得形如程序集包含哪些类型,类型包 含哪些方法等等大量的信息。但是反射也仅能够‘看’,而Emit则可以在运行时动态生成代码。接下来就来看看如何用Emit生成代码。

二、动态生成代码

  首先需要明确的是这里的代码并不是我们时常提到的C#,VB等源代码,而是IL代码。既然是IL代码,那学习Emit是不是要先对IL很熟悉 呢?诚然,熟悉IL代码对Emit学习会大有帮助,但是不懂也没关系,因为IL和高级语言一样,也是有一些相对固定的语法结构组成,不可能在一个IL程序 里表述if是一个样子而到另一个程序却变成了另一个样子。所以只要多用,多记,很快就能掌握这些东西。

  其次如C#,VB等程序会包含程序集,模块,类,方法,属性等元素一样,Emit生成的代码也包括这些元素。以下介绍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");
 

   运行生成的Main.exe效果如下:

 

  

 

 

 

 

分享到:
评论

相关推荐

    Emit学习之旅

    Emit学习之旅是一个深入探索C# Emit技术的教程,它为开发者提供了动态生成IL(Intermediate Language)代码的能力。Emit是.NET Framework的一部分,允许程序员在运行时构建和执行方法,这是实现元编程的重要工具。...

    Emit 学习资源汇总

    1. **Emit的基本使用**: 在Node.js中,EventEmitter类提供了Emit方法,用于触发事件。例如,创建一个EventEmitter实例,然后定义一个事件并触发它: ```javascript const { EventEmitter } = require('events')...

    EMIT学习经典实例

    1. **EMIT的基本概念** EMIT API提供了对.NET Framework中间语言(IL)的直接操作能力,让我们可以在运行时动态地创建类型、方法和属性。IL是.NET平台的二进制表示形式,它类似于汇编语言,但与具体的硬件无关。...

    Vue学习之旅之重点1:组件与数据传递

    在Vue.js的学习旅程中,组件化开发是其核心特性之一,极大地提高了代码的复用性和组织性。本篇文章将深入探讨Vue中的组件以及如何在组件间进行数据传递,旨在帮助初学者理解并掌握这一关键概念。 一、Vue组件 Vue...

    emit 类库 流畅api

    Emit类库是一种在.NET框架中用于动态代码生成的技术,主要通过System.Reflection.Emit命名空间中的类来实现。这个库提供了一种低级别的方法来构建IL(中间语言)指令,允许开发者在运行时创建类型、方法、属性等。...

    Emit实现AOP示例

    纯手工打造Emit实现AOP private static void OverrideMethods(TypeBuilder tb, MethodInfo method) { if (!method.IsPublic|| !method.IsVirtual || IsObjectMethod(method)) return; Type[] paramTypes = ...

    Emit创建动态类

    1. **TypeBuilder**: 这是Emit的核心类,用于在运行时构建类型。你可以设置类的名称、基类、接口、访问修饰符等属性,并定义字段、方法、属性。 2. **MethodBuilder**: 用于构建方法。你可以定义方法签名,然后使用...

    Emit常用Opcode指令使用方法

    除了在网上查资料之外学习MSIL另一个好方法就是.Net Reflector和ildasm.exe配合使用,.Net Reflector可以把Emit代码转换为普通C#代码,ildasm.exe可以把普通C#代码转换为MSIL,不会写某一功能的Emit代码就先把它的C#...

    Emit实现动态代理

    1. **定义委托**:首先,我们需要定义一个委托类型,这个委托类型代表我们要代理的方法。例如,如果有一个名为`Execute`的方法,我们可以定义一个`Action`或者`Func`类型的委托。 2. **创建动态类型**:使用`System...

    emit-response-size:将响应大小作为事件发送

    安装$ npm install emit-response-size用法const resSize = require ( 'emit-response-size' )const httpNdjson = require ( 'http-ndjson' )const stdout = require ( 'stdout-stream' )const http = require ( '...

    Emit实现从URL或者表单中创建对象

    在IT行业中,Emit是一个非常重要的概念,尤其在JavaScript或TypeScript等动态语言中,它通常与事件处理和对象创建有关。在这个特定的场景中,"Emit实现从URL或者表单中创建对象"指的是利用Emit机制来解析URL或表单...

    $emit触发事件拿不到传递的参数.zip

    在Vue.js框架中,`$emit`是用来在组件实例中触发自定义事件的,通常用于父子组件间的通信。然而,有时候我们可能会遇到一个问题,即在父组件中通过`$on`监听子组件触发的事件时,无法接收到传递的参数。这个问题可能...

    Emit语法简单实现

    Emit代码示例,包括含参、不含参、静态、非静态等8个例子

    signal PYQT5

    emit@resource://devtools/shared/event-emitter.js:178:15 emit@resource://devtools/shared/event-emitter.js:255:5 setNodeFront@resource://devtools/client/framework/selection.js:153:5 onDetached@resource:...

    使用MSIL采用Emit方式实现C#的代码生成与注入.rar

    gen.Emit(OpCodes.Ldarg_1); // Load second argument gen.Emit(OpCodes.Add); // Perform addition gen.Emit(OpCodes.Ret); // Return result Type dynamicType = typeBuilder.CreateType(); MethodInfo add...

    emit.js:JavaScript中的高效极简事件发射器

    emit.js JavaScript中的高效极简事件发射器。安装它可以与bower或npm一起使用: bower install emit.jsnpm install emit.js在HTML中包含emit.min.js ,并且在全局范围内现在可以使用emit对象: < script type =" ...

    vue学习 三种常用传值方式 $emit的用法 slot与slot-scope

    1. **Props向下传递**: Props是Vue中父组件向子组件传递数据的主要方式。子组件通过props接收来自父组件的数据,并进行展示或处理。定义prop可以在子组件中声明,然后在父组件中通过属性绑定将数据传入。例如,子...

    vue 组件之间事件触发($emit)与event Bus($on)的用法说明

    组件之间事件触发 之前使用组件,并不是很频繁,是水平...父子组件之间事件触发可以使用$emit $emit的使用方法如下: 在子组件中,写一个click点击事件。比如: cancelCU() { this.dialogVisible = false; this.$emi

    微型ORM核心 用Emit转换数据集到List

    现在很流行微型ORM框架,嘎嘎~~ 自己也写了一个,这是其中的实体转换代码的一部分 使用Emit 动态编译CIL 效率挺高的~~

    [干货][EMIT]千行代码实现代理式AOP+属性的自动装配

    【干货分享】代理式AOP(面向切面编程)与属性自动装配是现代软件开发中的重要技术,...记住,理论学习是基础,动手实践才是王道,只有真正动手编写代码,才能真正掌握这些高级技术。所以,不要只是看,更要动手尝试!

Global site tag (gtag.js) - Google Analytics