玩转动态编译 - 高级篇:一,IL访问静态属性和字段

时间:2022-09-17 15:53:56
IL介绍

通用中间语言Common Intermediate Language,简称CIL,发音为"sill"或"kill")是一种属于通用语言架构和.NET框架的低阶(lowest-level)的人类可读的编程语言。目标为.NET 框架的语言被编译成CIL,然后汇编成字节码。CIL类似一个面向对象的汇编语言,并且它是完全基于堆栈的。它运行在虚拟机上,其主要的语言有C#、Visual Basic .NET、C++/CLI以及 J♯。

在.NET语言的测试版中,CIL原本叫做微软中间语言,即Microsoft Intermediate Language,简称MSIL。由于C#和通用语言架构的标准化,在.Net开发平台下,所有语言(C#、VB.NET、J#、Managed C++)都会被编译为MSIL,再由CLR负责运行,字节码现在已经官方地成为了CIL。因此,CIL仍旧经常与MSIL相提并论,特别是那些.NET语言的老用户。

以上引用自*

简单的来说,就是.NET在编译C#编码时会由一个“C#代码编译组件”将C#代码编译为IL中间码,然后再由另一套组件将IL中间码编译为字节码

利用Reflector查看IL源码

虽然IL码很复杂,不过编写一些简单的问题也不大。

下面就由浅入深的举几个栗子

我们先编译一个简单的方法AAA

using System;
using System.Collections.Generic;
using System.Text; namespace blqw.IL.Demo
{
class Program
{
static void Main(string[] args)
{ } public object AAA()
{
return MyClass.Name;
} } class MyClass
{
public static string Name { get; set; }
}
}

然后用reflector反射,看看生成的IL代码

玩转动态编译 - 高级篇:一,IL访问静态属性和字段

先找到AAA方法,然后在语言的下拉框中选择IL

分析IL

简单介绍一下每句IL的含义

.method public hidebysig instance object AAA() cil managed
{
.maxstack
.locals init (
[] obje-0001ct CS$$)//(以上的内容都不是我们需要关心的)
L_0000: nop //如果修补操作码,则填充空间。尽管可能消耗处理周期,但未执行任何有意义的操作。(也就是说这个步骤是么意义的)
L_0001: call string blqw.IL.Demo.MyClass::get_Name()//调用由传递的方法说明符指示的方法。(将返回值保存在堆栈中)
L_0006: stloc.0 //从计算堆栈的顶部弹出当前值并将其存储到索引 0 处的局部变量列表中。(将刚才的返回值,保存在locals[0],也就是那个CS$1$0000中)
L_0007: br.s L_0009//无条件地将控制转移到目标指令(类似于goto吧,跳转到下一行)
L_0009: ldloc.0 //将索引 0 处的局部变量加载到计算堆栈上。
L_000a: ret // 从当前方法返回,并将返回值(如果存在)从调用方的计算堆栈推送到被调用方的计算堆栈上。(这句必须是方法中的最后一句,指示方法已经结束了,这时如果堆栈中有值,则返回值,没有则返回void)
}

上面注释中括号里的都是我写的,括号外的是微软官方的注释

仔细看就会发现,其实编译好的IL生成了很多不必要的操作

比如L_0000.连微软自家都说这个操作是无意义的,尽可能消耗时间而已..

还有L_0006~L_0009.都是无用的;连起来看就会发现,它先把堆栈中的值保存到变量0中,然后goto到下一行,然后再把变量0的值加载到堆栈上,结果堆栈上原来是什么值还是什么值

所以真正有用的只有L_0001和L_000a,一句是执行方法,一句是返回

在C#中编写IL访问静态属性

好了现在我们按照这个思路自己写一个IL,只有2句啊,超简单有木有!!!!!!

public static Func<object> ILTest()
{
//编译li动态方法
//1.声明动态方法对象DynamicMethod
// 第一个参数是方法名,可以为空字符,不可以是null
// 第二个参数是动态方法返回值类型
// 第三个参数是Type[],表示方法参数,如果没有参数可以是null
// 第四个参数是声明逻辑关联类,可以让动态方法访问该类有权访问所有字段,包括逻辑关联类的私有字段
var dm = new DynamicMethod("", typeof(object), null, typeof(MyClass));
//2.声明il编译器
var il = dm.GetILGenerator();
//3.执行MyClass类的Name属性的Get方法 这句对应刚才的L_0001
il.Emit(OpCodes.Call, typeof(MyClass).GetProperty("Name").GetGetMethod());
//4.方法结束,这句对应刚才的L_000a
il.Emit(OpCodes.Ret);
//5.由il编译器创建指定类型的动态方法委托
return (Func<object>)dm.CreateDelegate(typeof(Func<object>));
}

去掉注释和方法声明,整个方法体才5行啊!

查看下运行结果

玩转动态编译 - 高级篇:一,IL访问静态属性和字段

成功了!而且由于自己编写IL比微软生成好的代码少了好多,所以性能上更好

编写IL访问静态字段

下面用一样的方法看看静态字段怎么访问

第一步,直接把MyClass的Name属性改为静态字段

class MyClass
{
public static string Name;
}

查看反编译的IL代码

.method public hidebysig instance object AAA() cil managed
{
.maxstack
.locals init (
[] object CS$$)
L_0000: nop
L_0001: ldsfld string blqw.IL.Demo.MyClass::Name
L_0006: stloc.0
L_0007: br.s L_0009
L_0009: ldloc.0
L_000a: ret
}

好吧,只有L_0001不一样了

对应C#中的代码更变:

public static Func<object> ILTest()
{
//编译li动态方法
//1.声明动态方法对象DynamicMethod
// 第一个参数是方法名,可以为空字符,不可以是null
// 第二个参数是动态方法返回值类型
// 第三个参数是Type[],表示方法参数,如果没有参数可以是null
// 第四个参数是声明逻辑关联类,可以让动态方法访问该类有权访问所有字段,包括逻辑关联类的私有字段
var dm = new DynamicMethod("", typeof(object), null, typeof(MyClass));
//2.声明il编译器
var il = dm.GetILGenerator();
//3.执行MyClass类的Name属性的Get方法 这句对应刚才的L_0001
//il.Emit(OpCodes.Call, typeof(MyClass).GetProperty("Name").GetGetMethod());
//OpCodes.Ldsfld : 将静态字段的值推送到计算堆栈上。
il.Emit(OpCodes.Ldsfld, typeof(MyClass).GetField("Name"));
//4.方法结束,这句对应刚才的L_000a
il.Emit(OpCodes.Ret);
//5.由il编译器创建指定类型的动态方法委托
return (Func<object>)dm.CreateDelegate(typeof(Func<object>));
}

执行结果和刚才一样的就不贴了

动态方法访问私有属性和字段

其实在刚才的方法注释中我就已经提到了,第三个参数是"逻辑关联类"

这个可以理解成你把这个动态方法编译到哪个类中去了,在这个动态方法中可以访问逻辑关联类可以访问的所有对象

把刚才的栗子改为私有的试下?

class MyClass
{
static string Name; //这个方法是为了给Name赋值用
public static void SetName(string name)
{
Name = name;
}
}
public static Func<object> ILTest()
{
//编译li动态方法
//1.声明动态方法对象DynamicMethod
// 第一个参数是方法名,可以为空字符,不可以是null
// 第二个参数是动态方法返回值类型
// 第三个参数是Type[],表示方法参数,如果没有参数可以是null
// 第四个参数是声明逻辑关联类,可以让动态方法访问该类有权访问所有字段,包括逻辑关联类的私有字段
var dm = new DynamicMethod("", typeof(object), null, typeof(MyClass));
//2.声明il编译器
var il = dm.GetILGenerator();
//3.执行MyClass类的Name属性的Get方法 这句对应刚才的L_0001
//il.Emit(OpCodes.Call, typeof(MyClass).GetProperty("Name").GetGetMethod());
//OpCodes.Ldsfld : 将静态字段的值推送到计算堆栈上。
il.Emit(OpCodes.Ldsfld, typeof(MyClass).GetField("Name", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static));//这里稍微改一下,反射到私有静态字段是要添加额外参数的
//4.方法结束,这句对应刚才的L_000a
il.Emit(OpCodes.Ret);
//5.由il编译器创建指定类型的动态方法委托
return (Func<object>)dm.CreateDelegate(typeof(Func<object>));
}

玩转动态编译 - 高级篇:一,IL访问静态属性和字段

访问静态私有属性的方式基本雷同,反射私有属性的代码是:

typeof(MyClass).GetProperty("Name",System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static).GetGetMethod(true);
下期预告

昨天是情人节,早早就上床了

嗯..别误会,出去玩了下,累了,所以睡得比较早

.本来应该把静态属性和字段的设置也写完的,真的没时间的,马上要上班去了

下一篇再说属性设置吧,同时吧类型转换一起将了,到时候还可以搞点小破坏哈~~