当前位置: 澳门新濠3559 > 编程 > 正文

实际使用中,在代码中实现了程序集、模块、类

时间:2019-11-08 23:18来源:编程
读书Emit必不可缺的, 会使用到IL中间代码. 初见IL代码,让自个儿有风华正茂种汇编的痛感, 让小编想起了, 大学时, 学习8051的汇编语言.多的就不扯了, 直接进去正题, OpCodes指令集是还是不

读书Emit必不可缺的, 会使用到IL中间代码. 初见IL代码, 让自个儿有风华正茂种汇编的痛感, 让小编想起了, 大学时, 学习8051的汇编语言. 多的就不扯了, 直接进去正题, OpCodes指令集是还是不是有风流浪漫种令人提心吊胆的感觉, 那么多, 具体作者未有数过, 但是迟早是比8051的授命多不菲, 应该有200多少个吗, 不过在其实应用的进度中, 鲜明是用不到这么多的, 所以只要掌握一些常用的就丰富了, 其他的, 查资料就足以了(高校老师那个时候也是那样教的, 实际运用中, 也实在是这么的)

说说emit(中)ILGenerator

文/玄魂

在上生机勃勃篇博客(说说emit(上)基本操作卡塔 尔(阿拉伯语:قطر‎中,作者叙述了中央的本领实现上的急需,难度和指标约束都非常的小,搭建了大旨的气派。在代码中实现了程序集、模块、类型和措施的开创,唯生机勃勃的可惜是方法体。

方法体是情势内部的逻辑,大家需求将以此逻辑用IL代码描述出来,然后注入到方法体内部。这里自然地引出五个主题,IL代码和用来将Il代码注入到点子体内的工具(ILGenerator卡塔尔国。本篇博客将重视围绕那八个核心展开。不过那风流罗曼蒂克篇博客不容许将IL讲的很详细,只可以围绕ILGenerator的运用来教学。若想精晓IL的全貌,笔者想照旧要看ECMA的文书档案了(

一、示例

2.1 CIL指令简要介绍

此处我们经过多少个轻便例子来对IL指令有个起来的认知。

新建一个名字为“HelloWorld”的调控台项目,代码如清单2-1(就算在自身事先的篇章里用过HelloWorld来解释Il,即使无数篇博客都用过那些事例,不过自个儿大概乐此不疲的用它卡塔 尔(阿拉伯语:قطر‎。

代码项目清单2-1  HelloWorld

上生机勃勃篇的扫尾部分, 贴出了叁个国语注释版的OpCodes文件, 那大器晚成部分剧情跟那一个文件是有超级大关系的. 貌似, 贴在此风度翩翩篇更符合呢...

using System;

哦, 依然应当从示例里去在那早先讲

 

来源 : 

namespace HelloWorld

static void Sum(int sum, string sumStr)
{
            int a, b, c;
            a = 1;
            b = 2;
            c = 3;
            sum = a + b + c;
            sumStr = sum.ToString();
            Console.WriteLine(sumStr);

            for (int i = 0; i < c; i++)
            {
                if (i > b)
                {
                    Console.WriteLine("满足条件, 跳出循环");
                    break;
                }
            }
            Console.ReadKey();
}

{

 

    class Program

.method private hidebysig static void Sum(int32 sum, string sumStr) cil managed
{
        .maxstack 2   //定义函数代码所用堆栈的最大深度,也指Evaluation Stackk中最多能同时存在2个值
        .locals init (    //变量的声明, (此时已经把num,num2,num3,num4,flag存入了Call Stack中的Record Frame中)
            [0] int32 num,
            [1] int32 num2,
            [2] int32 num3,
            [3] int32 num4,
            [4] bool flag)

        L_0000: nop        //无任何操作, 可忽略
        L_0001: ldc.i4.1   //加载 常量1 到栈中(压栈)
        L_0002: stloc.0    //从栈中把 常量1 拿出来, 赋值给num(出栈, 此时栈中已经没有东西了)
        L_0003: ldc.i4.2   //加载 常量2 到栈中(压栈)
        L_0004: stloc.1 
        L_0005: ldc.i4.3 
        L_0006: stloc.2 

        L_0007: ldloc.0   //将num变量压栈
        L_0008: ldloc.1   //将变量num2压栈 (此时栈中有两个值, num2在上面, num在下面)
        L_0009: add       //将num,num2求和的结果压栈(求和的时候, 会把两个值都提取出来, 所以结束后, 栈中只有一个结果值)
        L_000a: ldloc.2   //将num3压栈
        L_000b: add       //将num3,(num+num2)求和, 并压栈, 此时栈中, 只有最后的结果值
        L_000c: starg.s sum //将栈顶的值传给传参sum(短格式)

        L_000e: ldarga.s sum  //加载sum的地址到堆栈上(短格式)
        L_0010: call instance string [mscorlib]System.Int32::ToString()  //调用ToString()方法, 完成格式转换,将结果值放入堆栈中
        L_0015: starg.s sumStr   //将堆栈顶的值传给传参sumStr(短格式)

        L_0017: ldarg.1   //将索引为1的传参(sumStr)加载到堆栈中
        L_0018: call void [mscorlib]System.Console::WriteLine(string)  //调用Console.WriteLine方法

        L_001d: nop 
        L_001e: ldc.i4.0 
        L_001f: stloc.3      // i = 0
        L_0020: br.s L_0043 //无条件跳转到下面, 去判断 i<c 是否成立

        L_0022: nop 
        L_0023: ldloc.3   // i
        L_0024: ldloc.1   // b
        L_0025: cgt         // i > b ? 1 : 0
        L_0027: ldc.i4.0  //压栈0
        L_0028: ceq         //比较的结果在与0比较, (i > b ? 1 : 0) == 0 ? 1 : 0
        L_002a: stloc.s flag  //将结果存入本地变量flag
        L_002c: ldloc.s flag  //加载flag到堆栈中
        L_002e: brtrue.s L_003e //为真跳转到 L_003e

        L_0030: nop 
        L_0031: ldstr "u6ee1u8db3u6761u4ef6, u8df3u51fau5faau73af" //"满足条件, 跳出循环"
        L_0036: call void [mscorlib]System.Console::WriteLine(string)
        L_003b: nop 
        L_003c: br.s L_004d

        L_003e: nop 
        L_003f: ldloc.3    // i
        L_0040: ldc.i4.1  // 1
        L_0041: add        // i + 1
        L_0042: stloc.3   // i = i + 1

        L_0043: ldloc.3  // i
        L_0044: ldloc.2  //c
        L_0045: clt          // i < c ? 1 : 0
        L_0047: stloc.s flag  //将结果传给 flag
        L_0049: ldloc.s flag  //加载flag变量到堆栈中
        L_004b: brtrue.s L_0022  //为真跳转 L_0022

        L_004d: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
        L_0052: pop   //移除当前位于计算堆栈顶部的值
        L_0053: ret     //即为  return  标记 返回值
}

    {

ldc.i4.1:  i4--int32, 1--数值, 合起来正是 加载int32的数值1到库房中

        static void Main(string[] args)

stloc.0: 0--前边注解的locals变量组中的第0个  将宾馆顶的值付给locals0变量

        {

ldloc.0: 加载locals0变量到商旅中

            Console.WriteLine("Hello World");

add : 将栈顶的多少个值求和, 并将结果压栈

        }

二、常用的一声令下

    }

维基百科:

}

编写翻译上边的代码,然后使用ILDasm张开HelloWorld.exe,导出.il文件,内容如下:

来源:

//  Microsoft (R) .NET Framework IL Disassembler.  Version 4.0.30319.1

1. 常用的加载类指令

//  Copyright (c) Microsoft Corporation.  All rights reserved.

ldarg (及多个变化形式)

ld -- load , arg -- argument, 对这个大家都不陌生吧, 就不多解释了

加载方法的参数的值到栈中。除了泛型ldarg(需要一个索引作为参数),还有后其他很多的变化形式。'.'有个数字后缀的ldarg操作码来指定需要加载的参数。

a -- address, s -- short

ldarga/ldarga.s表示的是加载参数的地址, 而不是加载参数的值

ldc (及多个变化形式)

c -- constant, const这个关键字大家肯定都很熟了, constant表示常量

加载一个常数到栈中

Ldc.I4.2   i4表示是int32的值(1个表示8位), 2表示常量

ldfld (及多个变化形式) 加载一个对象实例的成员到栈中
ldloc (及多个变化形式)

loc -- locals

加载一个本地变量到栈中

ldobj 获得一个堆对象的所有数据,并将它们放置到栈中. OpCodes:将地址指向的值类型对象复制到计算堆栈的顶部。
ldstr 加载一个字符串数据到栈中

// Metadata version: v4.0.30319

 

.assembly extern mscorlib

  1. 常用的弹出操作指令

{

pop  删除当前栈顶的值,但是并不影响存储的值
starg

st -- store

存储栈顶的值到给出方法的参数,根据索引确定这个参数. OpCodes:将位于计算堆栈顶部的值存储到位于指定索引的参数槽中

stloc (及多个变化形式) 弹出当前栈顶的值并存储在一个本地变量列表中,根据所以确定这个参数
stobj 从栈中复制一个特定的类型到指定的内存地址
stfld 用从栈中获得的值替换对象成员的值

  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .zV.4..

 

  .ver 4:0:0:0

 3. 常用的其余操作类指令

}

add, sub, mul, div, rem

用于两个数加减乘除求模, 并将结果推送到计算堆栈上 

and, or, not, xor 用于在两个值上进行二进制操作
ceq, cgt, clt

用不同的方法比较两个在栈上的值

c -- compare

ceq:是否相等 -- 如果这两个值相等,则将整数值 1 (int32) 推送到计算堆栈上;否则,将 0 (int32) 推送到计算堆栈上

cgt:是否大于 -- 如果第一个值大于第二个值,则将整数值 1 (int32) 推送到计算堆栈上;反之,将 0 (int32) 推送到计算堆栈上。

cgt.un -- 比较两个无符号的或不可排序的值, un -- unsigned 无符号

clt:是否小于 -- 如果第一个值小于第二个值,则将整数值 1 (int32) 推送到计算堆栈上;反之,将 0 (int32) 推送到计算堆栈上。

box, unbox

在引用类型和值类型之间转换

box: 装箱

unbox: 拆箱

ret 退出方法和返回一个值
beq, bgt,bge,ble, blt, switch

控制方法中的条件分支

b -- break, eq,e -- equal

beq:如果两个值相等,则将控制转移到目标指令;

bgt:如果第一个值 > 第二个值,则将控制转移到目标指令

bge:如果第一个值 >= 第二个值,则将控制转移到目标指令

ble:如果第一个值 <= 第二个值,则将控制转移到目标指令

blt:如果第一个值 < 第二个值,则将控制转移到目标指令

switch:实现跳转表

所有的分支控制操作码都需要给出一个CIL代码标签作为条件为真的跳转目的地

brtrue

如果 value 为 true、非空或非零,则将控制转移到目标指令

br/br.s

br:(无条件)中止到代码标签

br.s:无条件地将控制转移到目标指令(短格式)

call 调用一个成员
newarr, newobj

在内存中创建一个新的数组或新的对象类型

newarr:将对新的从零开始的一维数组(其元素属于特定类型)的对象引用推送到计算堆栈上

newobj:创建一个值类型的新对象或新实例,并将对象引用(O 类型)推送到计算堆栈上

.assembly HelloWorld

 

{

未完待续......

 //(略)

}

.module HelloWorld.exe

// MVID: {CBB65270-D266-4B29-BAC1-4F255546CDA6}

.imagebase 0x00400000

.file alignment 0x00000200

.stackreserve 0x00100000

.subsystem 0x0003       // WINDOWS_CUI

.corflags 0x00020003    //  ILONLY 32BITREQUIRED

// Image base: 0x049F0000

 

 

// =============== CLASS MEMBERS DECLARATION ===================

 

.class private auto ansi beforefieldinit HelloWorld.Program

       extends [mscorlib]System.Object

{

  .method private hidebysig static void  Main(string[] args) cil managed

  {

    .entrypoint

    // Code size       13 (0xd)

    .maxstack  8

    IL_0000:  nop

    IL_0001:  ldstr      "Hello World"

    IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)

    IL_000b:  nop

    IL_000c:  ret

  } // end of method Program::Main

 

  .method public hidebysig specialname rtspecialname

          instance void  .ctor() cil managed

  {

    // Code size       7 (0x7)

    .maxstack  8

    IL_0000:  ldarg.0

    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()

    IL_0006:  ret

  } // end of method Program::.ctor

 

} // end of class HelloWorld.Program

在上头的代码中,掩瞒的剧情为AssemblyInfo.cs中剧情,约等于前后相继集级其余布署内容。首先注意以”.”起头的字段,.assembly、.module、.class、.method等等,大家誉为CIL指令(CIL Directive卡塔尔。和下令一起使用的,通常间接跟在指令前边的,称之为CIL 性子(CIL Attributes卡塔 尔(英语:State of Qatar),上边代码中的 extern,extends、private、public都归于CIL个性,它们的效果是用来描述CIL指令怎么样被试行。下边先从CIL指令(CIL Directive卡塔尔的角度省视下边包车型的士代码都告知了作者们如何音信。

.assembly extern mscorlib

{

  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                        

  .ver 4:0:0:0

}

当前前后相继集引用了前后相继集mscorlib,该程序集的强名称签字公钥标志为“B7 7A 5C 56 19 34 E0 89”,版本为“4:0:0:0”。

.assembly HelloWorld

{

 //(略)

}

概念当前景序集,名字为HelloWorld。

.module HelloWorld.exe

模块为.module HelloWorld.exe。

.imagebase 0x00400000

印象文件基址。

.file alignment 0x00000200

文本对齐大小。

.subsystem 0x0003       // WINDOWS_CUI

内定程序供给的应用程序情况。

.stackreserve 0x00100000

调用仓库(Call Stack)内存大小。

.corflags 0x00020003    //  ILONLY 32BITREQUIRED

保留字段,未使用。

.class private auto ansi beforefieldinit HelloWorld.Program

       extends [mscorlib]System.Object

申明类HelloWorld.Program。private是探望类型,auto指明内部存款和储蓄器布局项目,auto表示内部存款和储蓄器布局由.NET自动决定(LayoutKind,共有八个值:Sequential,Auto和Explicit卡塔 尔(英语:State of Qatar),ansi表示在托管和非托管调换时接收的编码类型。extends代表继续。

.method private hidebysig static void  Main(string[] args) cil managed

.method,评释方法;private,访问类型;hidebysig,相当于c#方法修饰符new;static,静态方法;void ,重回类型;cil managed,表示托管执行。

.entrypoint

次第入口点。

.maxstack  8

奉行格局时的测算货仓大小。

在点子内部,试行逻辑的编码,被称作操作码(Opcode,Operation Code卡塔 尔(阿拉伯语:قطر‎,如nop,ldstr。操作码也见怪不怪被翻译为命令,不过它的立陶宛(Lithuania卡塔尔语是Instruction实际不是Directive,本文称之为操作指令。完整的操作码速查手册,可参考。

操作码实际上都以二进制指令,每个指令有其对应的命名,比方操作码0x72对应的名号为ldstr。在操作码前面相符“IL_0000:”这个以冒号结尾的单元是(标签卡塔 尔(阿拉伯语:قطر‎Label,其值能够随性所欲钦命,在执行跳转时会用到Label。

在操作码以前,都会先安装计算货仓大小。计算仓库(Evaluation Stack卡塔尔是用来保存局地变量和章程传海腴数的长空。在艺术实施前后都要确认保证总计货仓为空。

从内部存款和储蓄器中拷贝数据到总计旅社的操作称之为Load,以ld初步的操作指令履行的都以load操作,比方ldc.i4为加载二个31个人整型数到总括货仓中,Ldargs.3为将引得为3的参数加载到总括仓库上。

从计算仓库拷贝数据回内部存款和储蓄器的操作为Store,以st起头的操作指令奉行的操作都以Store,比如stloc.0为从总计仓库的顶上部分弹出当前值并将其积攒到目录 0 处的部分变量列表中,starg.s为将身处总计旅社顶端的值存款和储蓄在参数槽中的钦点索引处。

在方法体的发端有的,供给钦定在艺术实践进度中须求的计算旅社的最大值,相当于.maxstack指令(directive卡塔 尔(阿拉伯语:قطر‎。在地点的身体力行程序中,大家钦定最大酒馆值为8,事实上它是编写翻译器钦点的暗中认可值。总括运算仓库的大大小小最轻巧易行的办法是总计形式参数和变量的个数,可是个数往往超出实际供给的仓库大小。编写翻译器往往会对代码做编写翻译优化,使钦点的货仓大小更客观(最大使用大小卡塔 尔(英语:State of Qatar)。举例上面包车型地铁代码

   staticvoid Main(string[] args)

        {

            int v1 = 0;

            int v2 = 0;

            int v3 = 0;

            int v4 = 0;

            int v5 = 0;

            int v6 = 0;

            int v7 = 0;

            int v8 = 0;

            int v9 = 0;

            int v10 = 0;

            Console.WriteLine("Hello World");

        }

 

澳门新濠3559 1

 

编写翻译之后,编写翻译器设置的预计仓库为大小为1。

改革成上边的代码之后,总计仓库的大大小小是不怎么啊?

  classProgram

    {

        staticvoid Main(string[] args)

        {

            int v1 = 0;

            int v2 = 0;

            int v3 = 0;

            int v4 = 0;

            int v5 = 0;

            int v6 = 0;

            int v7 = 0;

            int v8 = 0;

            int v9 = 0;

            int v10 = 0;

            UseParams(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10);

            Console.WriteLine("Hello World");

        }

 

        privatestaticvoid UseParams(int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9, int v10)

        {

            int sum = v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10;

        }

 

    }

初步总括Main方法的乘除旅社的高低应该是11(变量个数卡塔 尔(阿拉伯语:قطر‎,可是最大使用量是10,所以最后最大计算仓库的抑扬顿挫应该是10。

 

澳门新濠3559 2

 

 

实在使用计算货仓的规范很简短,在行使变量此前将其压栈,使用后弹栈

那边再啰嗦一句,个人认为读书Il编码的最简便易行方法是先领悟基本原理,酌量意气风发份指令表,用C#编纂实例代码,然后选拔反编写翻译工具反编译查看Il指令,最终再本人模仿编写。

现今大家回头看最轻巧易行的HelloWorld程序的个中IL实现。

   .entrypoint

    // Code size       13 (0xd)

    .maxstack  8

    IL_0000:  nop

    IL_0001:  ldstr      "Hello World"

    IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)

    IL_000b:  nop

    IL_000c:  ret

逐句解释下。

IL_0000:  nop

不进行别的push可能pop操作

ldstr      "Hello World"

加载字符串"Hello World"的援引到总括仓库。

call       void [mscorlib]System.Console::WriteLine(string)

调用程序集为mscorlib中的System.Console类的点子WriteLine。这时会自动掸出总计货仓中的值赋值为调用方法的参数。

IL_000c:  ret

ret就是return,甘休近些日子艺术,再次来到再次回到值。

上面我们再来看多个小例子,加深下明白。

  staticvoid Main(string[] args)

        {

            int v1 = 2;

            object v2 = v1;

            Console.WriteLine((int)v2);

        }

这段代码,涉及叁个粗略的赋值操作和三个装箱拆箱。大家看对应的IL代码:

.method private hidebysig static      void Main (         string[] args     ) cil managed  {     // Method begins at RVA 0x2050     // Code size 23 (0x17)     .maxstack 1     .entrypoint     .locals init (         [0] int32 v1,         [1] object v2     )     IL_0000: nop     IL_0001: ldc.i4.2     IL_0002: stloc.0     IL_0003: ldloc.0     IL_0004: box [mscorlib]System.Int32     IL_0009: stloc.1     IL_000a: ldloc.1     IL_000b: unbox.any [mscorlib]System.Int32     IL_0010: call void [mscorlib]System.Console::WriteLine(int32)     IL_0015: nop     IL_0016: ret } // end of method Program::Main

  首先是有个别变量的扬言,IL会在每方法的最上部注脚全体的部分变量,使用.locals init。

 .locals init ( [0] int32 v1,[1] object v2 )

在演示中宣称了v1和v2八个部分变量。事实上这里不光是宣称这么轻易,这里不可不要开拓内部存款和储蓄器空间, 若要开荒内部存储器空间必要求赋值,也便是说评释的还要要拓宽赋值,那正是暗中认可值的由来。那么些操作正是指令中的init 完结的。更加深切的分析,请参考。

先是个赋值操作int v1 = 2;是何等达成的吗?

1)        ldc.i4.2,加载叁10个人整型数2到总括饭馆;

2)        stloc.0,从总括旅舍顶端弹出值赋值到部分变量列表中的第一个变量。

再看第二条语句object v2 = v1的落到实处进度

1)        ldloc.0,加载局地变量列表中的三个变量到总结仓库中;

2)        box [mscorlib]System.Int32,对计量货仓中的最上端值实践李装运箱操作

3)        stloc.1,从计算货仓顶端弹出值赋值给一部分变量列表中的第二个变量

其余操作看似,就不做解释了。

大家再看贰个while循环的操作,掌握循环是什么完毕的,c#代码如下:

  staticvoid Main(string[] args)

        {

            int i = 0;

            while (i < 5)

            {

                i++;

            }

        }

对应的Il代码为:

.method private hidebysig static          void Main (             string[] args         ) cil managed      {         // Method begins at RVA 0x2050         // Code size 20 (0x14)         .maxstack 2         .entrypoint         .locals init (             [0] int32 i,             [1] bool CS$4$0000         )         IL_0000: nop         IL_0001: ldc.i4.0         IL_0002: stloc.0         IL_0003: br.s IL_000b         // loop start (head: IL_000b)             IL_0005: nop             IL_0006: ldloc.0             IL_0007: ldc.i4.1             IL_0008: add             IL_0009: stloc.0             IL_澳门新濠3559,000a: nop             IL_000b: ldloc.0             IL_000c: ldc.i4.5             IL_000d: clt             IL_000f: stloc.1             IL_0010: ldloc.1             IL_0011: brtrue.s IL_0005         // end loop         IL_0013: ret     } // end of method Program::Main

率先注解了八个bool类型的有的变量([1] bool CS$4$0000卡塔 尔(阿拉伯语:قطر‎。在循环起来以前,先实践 

  IL_0003: br.s IL_000b

跳转到IL_000b处。先加载变量到总括仓库:

   IL_000b: ldloc.0             IL_000c: ldc.i4.5

然执行相比较操作:

            IL_000d: clt

假设第3个值小于第二值,将整数1压入总计仓库,不然将整数0压入总括饭店,同一时间施行弹栈操作。接下来将相比较结实赋值给部分变量列表中的第2个变量:

  IL_000f: stloc.1

从今未来再将部分变量列表中的第三个变量压栈:

 IL_0010: ldloc.1

接下来判别bool值,明确是否推行循环体内代码:

  IL_0011: brtrue.s IL_0005

如果为true,跳转到 IL_0005处,然后继续试行加法操作:

   IL_0005: nop             IL_0006: ldloc.0             IL_0007: ldc.i4.1             IL_0008: add             IL_0009: stloc.0             IL_000a: nop

  IL_000a: nop实施之后再从 IL_000b处开始新后生可畏轮的循环。

有关IL指令的牵线就到这里,不然就收不住笔了,越写越来越多。未来把思路收回到Emit,当大家询问相关C#代码怎么样用Il实现之后,下一步正是考虑将Il之类植入方法内部,在Emit的历程中,大家拿什么来发挥要植入的Il指令呢?是原生的字符串吗?当然不是,.NET计划了OpCodes 类,该类以字段的样式封装了操作码。

 

澳门新濠3559 3

 

 

消释了写操作码的主题素材,下八个主题材料正是哪些爆发(Emit卡塔尔国操作码到点子内部了?那便是ILGenerator类。

2.2  ILGenerator

ILGenerator类的效果,一句话,生成IL代码。想要获取ILGenerator类的实例,唯有四个门路:

1)        ConstructorBuilder.GetILGenerator方法

2)        DynamicMethod.GetILGenerator 方法

3)        MethodBuilder.GetILGenerator 方法

上边提到到了在Emit中能够动态变化方法的两种路子,ConstructorBuilder类用来布局的构造函数,构造函数内部的IL要接纳它的GetILGenerator方法再次来到的ILGenerator类发出。DynamicMethod类,是在当下运作上下文情况中动态变化方法的类,使用该类不必事先创造程序集、模块和花色,相近发出个中间的IL使用DynamicMethod.GetILGenerator方法重临的ILGenerator类实例。MethodBuilder作者在《说说emit(上)基本操作中做了介绍,写到这里,突然意识很正剧的是,竟然从未主意很顺遂的和上篇博客很顺遂的衔接起来。看来写小说也是要尊敬设计的。既然不可能很好的衔接,也就不强求了,这里将上篇博客提到的以身作则糅合到一同,完结多少个至上轻便的Mock接口的例子。

自身要落到实处的调用效果是那般的:

  Mock<IAssessmentAopAdviceProvider> mocker = newMock<IAssessmentAopAdviceProvider>();

  mocker.Setup(t => t.Before(3)).Returns("HelloWorld!");

  Console.WriteLine(mocker.Obj.Before(2));

吸收接纳贰个接口,初叶化多个Mock类的实例,然后经过Setup和Returns扩大方法设定落成该接口的实例在钦赐方法上的重临值。这里大家先不思索对不一致参数的拍卖逻辑。

Mock类的概念如下:

  publicclassMock<T>

    {

        public T Obj

        {

            get;

            set;

        }

        publicSetupContext Contex { get; set; }

        public Mock()

        {

        }

    }

Mock类的Obj属性是一定接口的实例。Contex属性是上下文新闻,当前内容很简单,只包蕴叁个MethodInfo属性。定义如下:

  publicclassSetupContext

    {

        publicMethodInfo MethodInfo { get; set; }

}

本条上下文音信前段时间只满足接口有一个方法的状态,对应的相干落实也只寻思三个主意,在此个示例程序中我们不必要过分郁结其余细节,避防乱了前后相继。

接下去是八个扩展方法。

  publicstaticclassMockExtention

    {

        publicstaticMock<T> Setup<T>(thisMock<T> mocker, Expression<Action<T>> expression)

        {

            mocker.Contex = newSetupContext();

            mocker.Contex.MethodInfo = expression.ToMethodInfo();

            return mocker;

        }

 

        publicstaticvoid Returns<T>(thisMock<T> mocker, object returnValue)

        {

            if (mocker.Contex != null && mocker.Contex.MethodInfo != null)

            {

                //这里为简易起见,只思谋IAssessmentAopAdviceProvider接口

             mocker.Obj=  (T)AdviceProviderFactory.GetProvider(mocker.Contex.MethodInfo.Name,(string)returnValue);

            }

          

        }

 

        publicstaticMethodInfo ToMethodInfo(thisLambdaExpression expression)

        {

            var memberExpression = expression.Body as System.Linq.Expressions.MethodCallExpression;

;

            if (memberExpression != null)

            {

                return memberExpression.Method;

            }

            returnnull;

        }

    }

Setup是Mock类的恢宏方法,配置要Mock的艺术信息;Returns扩张方准绳调取对应的工厂获取接口的实例。

ToMethodInfo是拉姆daExpression增添方法,该方式从Lambda表明式中收获MethodInfo。

此间对应的靶子工厂也轻松化,直接回到IAssessmentAopAdviceProvider接口实例。

 

澳门新濠3559 4

 

 

第生龙活虎,在构造函数中,早先化assemblyBuilder和moduleBuilder,代码如下:

    static AdviceProviderFactory()

        {

                       assemblyName.Version = newVersion("1.0.0.0");

            assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);

            moduleBuilder = assemblyBuilder.DefineDynamicModule("MvcAdviceProviderModule", "test.dll",true);

 

        }

上面包车型客车代码就不解释了,相关内容在前生机勃勃篇博客有详尽的分解。

GetProvider方法当前从未有过其余逻辑,只是调用了CreateInstance方法。

代码如下:

publicstaticIAssessmentAopAdviceProvider GetProvider(string methodName,string returnValue)

        {

            //创制接口的实例

          return  CreateInstance("MvcAdviceReportProviderInstance",methodName,returnValue);

           

        }

CreateInstance方法担负创制项目和办法的贯彻:

privatestaticIAssessmentAopAdviceProvider CreateInstance(string instanceName,string methodName,string returnValue)

        {

 

            TypeBuilder typeBuilder = moduleBuilder.DefineType("MvcAdviceProvider.MvcAdviceProviderType", TypeAttributes.Public, typeof(object), newType[] { typeof(IAssessmentAopAdviceProvider) });

           // typeBuilder.AddInterfaceImplementation(typeof(IAssessmentAopAdviceProvider));

            MethodBuilder beforeMethodBuilder = typeBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.Virtual, typeof(string), newType[] { typeof(int) });

                beforeMethodBuilder.DefineParameter(1, ParameterAttributes.None ,"value");

           

                ILGenerator generator1 = beforeMethodBuilder.GetILGenerator();

              

              LocalBuilder local1=  generator1.DeclareLocal(typeof(string));

              local1.SetLocalSymInfo("param1");

                generator1.Emit(OpCodes.Nop);

                generator1.Emit(OpCodes.Ldstr, returnValue);

                generator1.Emit(OpCodes.Stloc_0);

                generator1.Emit(OpCodes.Ldloc_0);

                generator1.Emit(OpCodes.Ret);

              

 

                Type providerType = typeBuilder.CreateType();

                assemblyBuilder.Save("test.dll");

              IAssessmentAopAdviceProvider provider = Activator.CreateInstance(providerType) asIAssessmentAopAdviceProvider;

 

              return provider;

 

           

        }

在CreateInstance方法中,首先定义类型:

TypeBuilder typeBuilder = moduleBuilder.DefineType("MvcAdviceProvider.MvcAdviceProviderType", TypeAttributes.Public, typeof(object), newType[] { typeof(IAssessmentAopAdviceProvider) });

注意第多个和第多个参数,分别是该项指标基类型和促成的接口列表。

接下来大家依照前面一个的办法名称和参数定义方法:

    MethodBuilder beforeMethodBuilder = typeBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.Virtual, typeof(string), newType[] { typeof(int) });

DefineMethod方法中,传人的第叁个参数是方法的名称,第一个参数是访谈类型,第多个参数是修饰符,第多少个参数是措施的参数类型列表。这里需求小心第1个参数,也便是艺术的修饰符,因为接口中的方法定义都以virtual的,所以在完成接口的时候,方法也必得注明为MethodAttributes.Virtual。

接下去定义方法的参数,使用MethodBuilder.DefineParameter方法。

                beforeMethodBuilder.DefineParameter(1, ParameterAttributes.None ,"value");

DefineParameter方法的首先参数钦命当前概念的参数在章程参数列表中的顺序,从1开首,假诺设置为0则象征办法的回到参数。首个参数是安装参数的特征,如输入参数,输出参数等等。第多个参数是点名该参数的名号。

方法定义完结,接下去就是发出Opcode,重回三个点名的字符串。先得到ILGenerator实例,如下:

ILGenerator generator1 = beforeMethodBuilder.GetILGenerator();

咱俩若想回到贰个字符串,必须先为该字符串声美素佳儿(Friso卡塔尔国个有的变量,能够行使LocalBuilder.DeclareLocal方法,如下:

    LocalBuilder local1=  generator1.DeclareLocal(typeof(string));

              local1.SetLocalSymInfo("param1");

ocal1.SetLocalSymInfo("param1")钦定局部变量的名号。

亟需小心,假诺模块定义时不允许发生符号音讯,是无助接纳SetLocalSymInfo方法的,AdviceProviderFactory的构造函数中,大家定义模块的代码

moduleBuilder = assemblyBuilder.DefineDynamicModule("MvcAdviceProviderModule", "test.dll",true);

最后三个参数是钦定是还是不是同意产生符号音讯的。

发出的Opcode很简单:

   generator1.Emit(OpCodes.Nop);

                generator1.Emit(OpCodes.Ldstr, returnValue);

                generator1.Emit(OpCodes.Stloc_0);

                generator1.Emit(OpCodes.Ldloc_0);

                generator1.Emit(OpCodes.Ret);

首先条指令不进行此外操作。

其次条指令加载贰个字符串到总计货仓中。

其三条指令赋值总括商旅顶上部分的数据到一些变量列表中的第三个变量。

第四条指令加载局地变量列表中的第二个变量的数额引用到总结货仓。

第五条指令方法重回。

全数Emit的历程停止了,接下去要开创实例:

   Type providerType = typeBuilder.CreateType();

                assemblyBuilder.Save("test.dll");

              IAssessmentAopAdviceProvider provider = Activator.CreateInstance(providerType) asIAssessmentAopAdviceProvider;

在上面的代码中,大家保留了模块,使用反编写翻译工具加载该模块,看看生成的代码是还是不是意料的。Il代码如下:

class public auto ansi MvcAdviceProvider.MvcAdviceProviderType     extends [mscorlib]System.Object     implements [EmitMock]EmitMock.IAssessmentAopAdviceProvider {     // Methods     .method public virtual          instance string Before (             int32 'value'         ) cil managed      {         // Method begins at RVA 0x2050         // Code size 9 (0x9)         .maxstack 1         .locals init (             [0] string         )         IL_0000: nop         IL_0001: ldstr "HelloWorld!"         IL_0006: stloc.0         IL_0007: ldloc.0         IL_0008: ret     } // end of method MvcAdviceProviderType::Before     .method public specialname rtspecialname          instance void .ctor () cil managed      {         // Method begins at RVA 0x2068         // Code size 7 (0x7)         .maxstack 2         IL_0000: ldarg.0         IL_0001: call instance void [mscorlib]System.Object::.ctor()         IL_0006: ret     } // end of method MvcAdviceProviderType::.ctor } // end of class MvcAdviceProvider.MvcAdviceProviderType

c#代码如下:

using EmitMock; using System; namespace MvcAdviceProvider {     public class MvcAdviceProviderType : IAssessmentAopAdviceProvider     {         public  string Before(int value)         {             return "HelloWorld!";         }     } }

谈起底,编写一个调控台程序来测量试验一下:

      staticvoid Main(string[] args)

        {

            EmitMock.Mock<IAssessmentAopAdviceProvider> mocker = newMock<IAssessmentAopAdviceProvider>();

            mocker.Setup(t => t.Before(3)).Returns("HelloWorld!");

            Console.WriteLine(mocker.Obj.Before(2));

          Console.Read();

        }

运维结果如下图:

 

澳门新濠3559 5

在下风流洒脱篇博客,不筹划继续介绍Emit的利用,在抱怨Emit的麻烦之余,是还是不是还会有此外选项吗?大家来谈一谈《Emit和Mono.cecil》。

 

编辑:编程 本文来源:实际使用中,在代码中实现了程序集、模块、类

关键词: