• Exploring the System.Reflection.Emit Namespace(翻译)


    System.Reflection.Emit命名空间提供了创建动态程序集的能力,动态程序集就是指在运行时创建的程序集。动态程序集能够被运行

    或者保存到磁盘上。处于这个原因,许多的.net编译器使用了 System.Reflection.Emit来提供代码生成。在这片文章中,我们将探索下

    System.Reflection.Emit命名空间,并构建一个简单的类库程序集


    System.Reflection.Emit命名空间概览

    类名:                      描述
    AssemblyBuilder     用来动态创建程序集
    ModuleBuilder     用来动态创建模块
    TypeBuilder     用来动态创建类型
    FieldBuilder     用来动态创建字段
    ConstructorBuilder     用来动态创建构造函数
    MethodBuilder     用来动态创建方法
    ILGenerator     用来生成il指令到动态创建的方法或构造函数中
    OpCodes     包含il指令的类
    Label (struct)     用来定义标签(用于il分支跳转)

    另外还有一些还有一些其他的类,本文并没有用到。

    类名:                        描述:
    CustomAttributeBuilder     用来创建自定义特性
    EnumBuilder     用来创建枚举
    EventBuilder     用来创建事件
    LocalBuilder     用来创建方法和构造函数用到的局部变量
    MethodRental     用来交换方法的实现
    ParameterBuilder     用来创建方法和构造函数用到的参数
    PropertyBuilder     用来创建类型的属性
    SignatureHelper     用来创建字段,方法,属性和局部签名
    UnmanagedMarshal     用来描述托管到非托管类型的封送 

       现在我们已经对System.Reflection.Emit命名空间有了一个大概印象,让我们使用这些类来构建一个简单的类库程序集,然后保存到磁
    盘中,最后在另一个程序中使用动态创建的程序集

    类库程序集代码
    这个我们将在运行时创建的类库提供了一个简单的类。这个类可以让我们从文件中读取一些警句名言,然后可以随机的取出名言。代码如下

    namespace QuoteOfTheDay
    {
        
    public class QuoteOfTheDay
        {
            
    private System.Collections.ArrayList m_Quotes = new System.Collections.ArrayList();
            
    private Random m_Random = new Random();
            
            
    public QuoteOfTheDay(string filename)
            {
                
    //根据文件名打开一个text reader
                System.IO.TextReader tr = System.IO.File.OpenText(filename);
                
    string        quote;
                
                
    // 将每一行的名言加入到数组链表中
                while ( (quote = tr.ReadLine()) != null)
                    m_Quotes.Add(quote);

                
    // 关闭 text reader
                tr.Close();
            }

            
    public string GetRandomQuote()
            {
                
    int count = m_Quotes.Count;
                
                
    // 如果没有任何名言返回一个空字符串
                if (count == 0)
                    
    return "";

                
    //否则从数组链表中返回一个随机的名言
                return (string) m_Quotes[ m_Random.Next(count) ];
            }
        }
    }


    可以看到这个类从文件中加载了所有的名言(每行一个),然后通过GetRandomQuote方法返回一个随机的名言。了解这个类后。我们
    将在运行时生成这个类,而非直接编译这段代码。

    创建动态程序集和模块

    这里我们将使用 "System.Reflection" 和"System.Reflection.Emit" 命名空间。我们可以很容易的通过调用 AppDomain.DefineDynamicAssembly() (有9个 重载)方法来创建动态程序集,下面我们将创建一个能够保存到磁盘的弱名称的程序集

    AssemblyName an = new AssemblyName();

    // 版本号 1.0.0.0
    an.Version = new Version(1000);
    // 设置程序集名称
    an.Name = "QuoteOfTheDay";

    // 定义动态程序集
    AssemblyBuilder ab = System.AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Save);


    以免有些人不熟悉程序集名称,有两种方法来命名程序集。1.弱名称,允许定义版本信息,文本名称,文化信息。2.强名称程序集,比弱
    名称多了一个公钥和数字签名。强名称程序集保证了名称的唯一性,而弱名称不能保证唯一性。更多关于强名称的信息请参考 msdn上"Strong-Named Assemblies"
    注意我们的程序集有"Save"访问权限。也就是说我们可以保存动态程序集到硬盘上。(如果是"Run"的话只能运行动态程序集不能保存到硬盘上,“RunAndSave”则保存运行都允许)现在我们有了动态程序集,下面就可以创建动态模块了。这里我将创建一个单模块程序集,所以我们的模块名称和程序集名一样。
    定义一个动态模块,我们将调用AssemblyBuilder.DefineDynamicModule():


    // 定义一个动态模块(名称和程序集一样)
    ModuleBuilder modBuilder = ab.DefineDynamicModule("QuoteOfTheDay""QuoteOfTheDay.dll");


    这个动态创建的模块将被保存为 QuoteOfTheDay.dll。默认程序集将被保存为动态链接库(dll)。要想生成可执行文件,必须调用
    AssemblyBuilder.SetEntryPoint()设置程序入口的方法 。调用这个方法允许你生成控制台或者windows应用程序程序集
    现在有了动态程序集和模块,下面就可以做些有意思的了。我们将定义在运行时定义QuoteOfTheDay 类型

    创建动态类型

    跟在动态程序集中定义动态模块一样,我们可以在动态模块中定义动态类型(这里指QuoteOfTheDay )

    // 创建 公有的QuoteOfTheDay 类型 (命名空间是 QuoteOfTheDay)
    TypeBuilder tb = modBuilder.DefineType("QuoteOfTheDay.QuoteOfTheDay", TypeAttributes.Class | TypeAttributes.Public);

    TypeAttributes允许我们制定类的访问修饰符(private,public,等)。当然也包括sealed,abstract等关键字。现在我们已经有了TypeBuilder对象,下面就可以使用它来构造我们的QuoteOfTheDay类了

    动态定义字段
    从上面类的c#代码可以看出。类有两个私有字段 m_Quotes 和 m_Random 我们可以在我们的QuoteOfTheDay类中定义这两个字段,代码如下

    FieldBuilder m_Quotes = tb.DefineField("m_Quotes"typeof(System.Collections.ArrayList), FieldAttributes.Private);
    FieldBuilder m_Random 
    = tb.DefineField("m_Random"typeof(System.Random), FieldAttributes.Private);

    TypeBuilder.DefineField方法接受三个参数  字段名称, 类型 , 特性(可见性..).下面开始定义类的构造函数

    定义动态类型的构造函数

    如果你不了解 msil也不用担心,下面的代码将非常简单

    / 创建构造函数(公有的并接受一个string参数)
    ConstructorBuilder cb 
    = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard,
                                   
    new Type[] { typeof(String) });

    //获取构造函数的IL 生成器对象
    ILGenerator ilgen = cb.GetILGenerator();
    // 定义局部变量tr
    ilgen.DeclareLocal(typeof(System.IO.TextReader));
    // 定义局部变量quote
    ilgen.DeclareLocal(typeof(System.String));
                
    // 加载 "this"指针到运行栈上
    ilgen.Emit(OpCodes.Ldarg_0);
    // 调用基类(Object)的默认无参构造函数
    ilgen.Emit(OpCodes.Call, typeof(Object).GetConstructor(new Type[0]));
                
    // 加载 "this"指针到运行栈上
    ilgen.Emit(OpCodes.Ldarg_0);
    // 创建新的ArrayList对象
    ilgen.Emit(OpCodes.Newobj, typeof(System.Collections.ArrayList).GetConstructor(new Type[0]));
    // 保存ArrayList对象到字段字段 "m_Quotes"
    ilgen.Emit(OpCodes.Stfld, m_Quotes);

    // 加载this
    ilgen.Emit(OpCodes.Ldarg_0);
    // 创建新的Random对象
    ilgen.Emit(OpCodes.Newobj, typeof(System.Random).GetConstructor(new Type[0]));
    // 保存Random对象到字段 "m_Random"
    ilgen.Emit(OpCodes.Stfld, m_Random);

    // 加载 "filename"参数到栈上
    ilgen.Emit(OpCodes.Ldarg_1);
    // 调用File.OpenText(string)方法
    ilgen.Emit(OpCodes.Call, typeof(System.IO.File).GetMethod("OpenText"));
    // 保存结果到局部变量 "tr"
    ilgen.Emit(OpCodes.Stloc_0);

    // 定义 loop and exit 标签(用于我们的while循环)
    Label loop = ilgen.DefineLabel();
    Label exit 
    = ilgen.DefineLabel();

    //标记 loop 标签
    ilgen.MarkLabel(loop);

    // 加载局部变量 "tr"到栈上
    ilgen.Emit(OpCodes.Ldloc_0);
    // 调用tr.ReadLine() 虚方法
    ilgen.Emit(OpCodes.Callvirt, typeof(System.IO.TextReader).GetMethod("ReadLine"));
    // 存储结果到局部变量"quote"到栈上
    ilgen.Emit(OpCodes.Stloc_1);
    // 加载局部变量"quote"到栈上
    ilgen.Emit(OpCodes.Ldloc_1);
    //  "quote" 是null 跳转到exit标签
    ilgen.Emit(OpCodes.Brfalse_S, exit);
                
    // 加载this到栈上
    ilgen.Emit(OpCodes.Ldarg_0);
    // 加载"m_Quotes" 字段到栈上
    ilgen.Emit(OpCodes.Ldfld, m_Quotes);
    // 加载局部变量 "quote"到栈上
    ilgen.Emit(OpCodes.Ldloc_1);
    // 调用m_Quotes.Add(object) 虚方法
    ilgen.Emit(OpCodes.Callvirt, typeof(System.Collections.ArrayList).GetMethod("Add"));
    //从运行栈弹出 m_Quotes.Add(object)调用结果,因为没用到
    ilgen.Emit(OpCodes.Pop);
    // 跳转到loop标签
    ilgen.Emit(OpCodes.Br_S, loop);
                
    // 标记exit标签
    ilgen.MarkLabel(exit);
    //加载局部变量 "tr"
    ilgen.Emit(OpCodes.Ldloc_0);
    // 调用tr.Close() 虚方法
    ilgen.Emit(OpCodes.Callvirt, typeof(System.IO.TextReader).GetMethod("Close"));
                
    // 返回
    ilgen.Emit(OpCodes.Ret);

    让我们检查下上面都干了些啥,首先我们使用 TypeBuilder.DefineConstructor()定义了一个public构造函数 ,接受一个字符串参数。一旦有了ConstructorBuilder,我们通过 ConstructorBuilder.GetILGenerator()获取了这个构造函数的IL生成器对象。IL生成器允许我们给构造函数生成msil指令。

    我们然后定义了两个局部变量tr(TextReader类型),quote(String类型)。因为我们不打算生成调试符号,所以这两个变量都没有命名。如果要生成调试符合,可以调用AssemblyBuilder.DefineDynamicModule()的重载方法,使用LocalBuilder.SetLocalSymInfo()(LocalBuilder是由 TypeBuilder.DeclareLocal()返回的)

    下面我们调用基类Object的构造函数。注意我们在调用基类构造函数前首先加载了第一个参数(OpCodes.Ldarg_0)到运行栈上。回忆下对象的方法,第一个隐藏的参数this是一个指向当前被调用对象的引用。传给QuoteOfTheDay 构造函数的字符串参数实际上是第二个参数。(使用OpCodes.Ldarg_1加载)

    我们接着初始化了一个ArrayList对象(通过调用ArrayList的默认构造函数).将它保存到m_Quotes字段。然后创建了Random对象,保存到m_Random字段。

    下面我定义了两个标签,标签将用来构建我们的while循环。loop标签是循环的开始,exit标签是循环的结束。这样就告诉了IL生成器将标签和标签紧跟的的指令偏移量关联(用于指令跳转)。下面的代码是我们的while循环的条件部分。我们调用tr.ReadLine()然后存储结果(读取文件的一行)到局部变量到quote.如果quote是null就跳转到exit。(指令brfalse.s 表示如果运行栈顶值是0, 跳转到标签关联的代码位置)

    如果quote不是null,调用m_Quotes.Add()将quote添加到链表中。注意Add方法后面的pop指令,Add方法会返回一个整数(标识新添项的索引)到运行栈上。如果我们不用它,必须将它弹出栈(否则函数返回时栈是非空,会抛出异常)。接着是无条件跳转到loop标签关联的指令接着循环。循环结束后最后返回。现在构造函数生成完了,下面接着生成 GetRandomQuote()


    动态定义类型的方法

    // Define the GetRandomQuote method
    MethodBuilder mb = tb.DefineMethod("GetRandomQuote", MethodAttributes.Public, CallingConventions.Standard,
                            
    typeof(System.String), new Type[0]);

    // Get the IL generator for the method
    ilgen = mb.GetILGenerator();
    // Declare the "count" local
    ilgen.DeclareLocal(typeof(int));
                
    // Define the cont label
    Label cont = ilgen.DefineLabel();

    // Load "this"
    ilgen.Emit(OpCodes.Ldarg_0);
    // Load field "m_Quotes"
    ilgen.Emit(OpCodes.Ldfld, m_Quotes);
    // Call m_Quotes.get_Count() (virtual)
    ilgen.Emit(OpCodes.Callvirt, typeof(System.Collections.ArrayList).GetMethod("get_Count"));
    // Store in local "count"
    ilgen.Emit(OpCodes.Stloc_0);

    // Load local "count"
    ilgen.Emit(OpCodes.Ldloc_0);
    // Branch if count is not 0
    ilgen.Emit(OpCodes.Brtrue_S, cont);

    // Load the string ""
    ilgen.Emit(OpCodes.Ldstr, "");
    // Return
    ilgen.Emit(OpCodes.Ret);
                
    // Mark the cont label
    ilgen.MarkLabel(cont);
    // Load "this"
    ilgen.Emit(OpCodes.Ldarg_0);
    // Load field "m_Quotes"
    ilgen.Emit(OpCodes.Ldfld, m_Quotes);
    // Load "this"
    ilgen.Emit(OpCodes.Ldarg_0);
    // Load field "m_Random"
    ilgen.Emit(OpCodes.Ldfld, m_Random);
    // Load local "count"
    ilgen.Emit(OpCodes.Ldloc_0);
    // Call m_Random.Next(int) (virtual)
    ilgen.Emit(OpCodes.Callvirt, typeof(System.Random).GetMethod("Next"new Type[] { typeof(int) }));
    // Call m_Quotes.get_Item(int) (virtual)
    ilgen.Emit(OpCodes.Callvirt, typeof(System.Collections.ArrayList).GetMethod("get_Item"));
    // Cast the result to string
    ilgen.Emit(OpCodes.Castclass, typeof(System.String));
    // Return
    ilgen.Emit(OpCodes.Ret);


    下面分析下上面代码,首先定义了一个public 的方法GetRandomQuote,然后通过MethodBuilder.GetILGenerator()获取这个方法的
    IL生成器对象.下面就开始生成方法体。首先定义一个int型局部变量(对应c#代码中的count),,然后定义一个标签cont ,如果if返回false的话就
    接着cont关联指令继续执行。然后将字段 m_Quotes 加载到运行栈上,调用ArrayList.get_Count 方法。回忆下get_xxx,set_xxx会映射到c#的xxx属性上。
    下面我们实现if语句,通过将count变量加载到运行栈上执行brtrue.s指令(如果count是非零,则跳转到指定标签关联指令(cont)
    ,否则接着执行后面的指令)。接着的另个指令是加载一个空字符串到运行栈然后返回

    如果count是非零,程序会跳转到cont标记的指令运行。首先加载m_Quotes到栈上,然后加载m_Random到栈上,下面调用
    m_Random.Next方法。这个方法返回一个随机数放到栈上,作为 m_Quotes.get_Item的参数。
    然后调用m_Quotes.get_Item(注意get_Item是索引器),返回的结果被转型为字符串。最后返回
    我们的类型构建完毕。现在我们就可以创建这个类型并保存程序集了

     创建动态类型和保存动态程序集

    在我们保存动态程序集到硬盘前,我们必须创建QuoteOfTheDay 类型。我们只要简单的调用TypeBuilder.CreateType()就ok了,代码如下
    // "Bake" the QuoteOfTheDay type
    tb.CreateType();

    生成最终的类型这一步骤是必须的。如果在保存动态程序集之前不调用CreateType(),就会出现
    System.NotSupportedException 告诉我们QuoteOfTheDay 类型不完整。下面就可以调用AssemblyBuilder.Save()方法保存程序集了

    // Save the assembly to the file specified
    ab.Save("QuoteOfTheDay.dll");

    好了终于完成了,如果你运行上面的代码,就会发现当前目录下多了一个新的QuoteOfTheDay.dll文件。你可以使用il反汇编器
    (ildasm.exe)反汇编这个dll查看生成的il代码来验证是否正确。

    使用生产的程序集

    我们可以创建一个控制台程序,添加对QuoteOfTheDay.dll的引用。Main方法中写如下代码

    QuoteOfTheDay.QuoteOfTheDay qotd = new QuoteOfTheDay.QuoteOfTheDay("quotes.txt");
    Console.WriteLine("Random Quote Of The Day: " + qotd.GetRandomQuote());

    运行程序就会输出随机的名言了。

    结论

    本文我们学会了如何创建动态程序集,模块,类型。 System.Reflection.Emit命名空间提供了非常简单干净的方式来生成代码,我

    们就不用学习复杂的PE文件格式了。所以如果感兴趣的话你可以为你最喜欢的语言写个.net编译器。


    原文:http://devhood.com/Tutorials/tutorial_details.aspx?tutorial_id=238

  • 相关阅读:
    Android中Scrollview、ViewPager冲突问题汇总(已解决)
    Android 关于ZXing的使用
    startActivityForResult用法详解
    SVN的使用(服务端与客户端)
    Genymotion安装常见问题
    Android Viewpager实现图片轮播(仿优酷效果)
    Android Shape 详解
    Android apktool反编译资源文件为空解决办法(测试天猫、淘宝等apk成功)
    查看CentOS版本
    新建git仓库并与github同步
  • 原文地址:https://www.cnblogs.com/xhan/p/1686642.html
Copyright © 2020-2023  润新知