探索CLR原理系列(3):方法元数据和IL(适合老鸟,新人勿沉迷其中)


前一篇我们探索了类型的第一种成员:字段。字段在IL编译时,会生成MdToken和偏移量,因为对于类型来说,一个类型在编译时就已经确定了字段的个数,所以偏移量对于编译器来说是已知的,字段和偏移量分别由元数据表(Field和ClassLayout)来记录。

本篇我们来讨论类型中的另一种成员:方法.在本系列的第一篇探索CLR原理系列(1):类型中我们说到类型中只有两种成员:字段和方法.字段是用来描述类型的状态,而方法则提供了类型所具备的功能.首先我们来看看方法在IL中是如何描述的.先来定义一个类型.

publicclassMethodClass

{

privatestringname="xulei";

privateint age=10;

publicstatic intAdd(int a,ref int b)//静态

{

return a + b;

}

privatestatic intSub(out int a,out int b)//私有静态

{

a =10;

b =5;

return a - b;

}

publicvoidWriteName()//公有

{

Console.Write(name);

}

protectedint GetAge()//受保护

{

return age;

}

privatevoidWriteNameAndAge()//私有

{

Console.Write(name + age.ToString());

}

这个类型中我们定义了2个不同访问级别静态方法,3个不同访问级别的实例方法.下面我们来看看这些方法的元数据。
TypeDef #2(02000003)

-------------------------------------------------------

TypDefName:TestDemo1.Type.MethodClass (02000003) //类型标识



Method#1(06000003) //方法标识

-------------------------------------------------------

MethodName: Add (06000003)

Flags : [Public][Static]

RVA : 0x00002064

ReturnType: I4

2 Arguments //参数列表

Argument #1: I4 //值传递

Argument #2:ByRefI4//引用传递

2Parameters

(1) ParamToken : (08000001) Name : aflags:[none]

(2) ParamToken : (08000002) Name : bflags:[none]



Method#2(06000004)

-------------------------------------------------------

MethodName:Sub(06000004)

Flags : [Private][Static]

RVA :0x0000207c

ReturnType:I4这部分黄色的就是方法签名
2Arguments

Argument #1:ByRefI4

Argument #2:ByRefI4

2Parameters 这部分是参数元数据表的内容

(1) ParamToken : (08000003) Name : aflags:[out]

(2) ParamToken : (08000004) Name : bflags:[out]



Method#3(06000005)

-------------------------------------------------------

MethodName:WriteName (06000005)

Flags :[Public]

RVA :0x00002091

hasThis 方法签名

ReturnType:Void

No arguments.



Method#4(06000006)

-------------------------------------------------------

MethodName:GetAge (06000006)

Flags :[Family]

RVA :0x000020a0

hasThis 方法签名

ReturnType:I4

No arguments.



Method#5(06000007)

-------------------------------------------------------

MethodName:WriteNameAndAge (06000007)

Flags : [Private]

RVA :0x000020b8

hasThis 方法签名

ReturnType:Void

No arguments.



可以看到在方法的原数据中包括了对方法的完整的描述,代码中高亮部分有二部分内容,方法返回值,方法参数.这二部分内容称之为方法签名,大家注意静态方法和实例方法有什么区别?仔细看一下,多了一个HasThis,关于原因我们在后面讨论.我们前一篇说过字段在Dll中有一个字段元数据表来描述所有的字段,那么方法也同样有一个方法的元数据表.来看看它是什么样子的.

探索CLR原理系列(3):方法元数据和IL(适合老鸟,新人勿沉迷其中)

方法的元数据表的Token是以0X06开头的,Token是IL中的标识数据,例如静态方法Add的标识为06000003,flags中标注了可见性,以及抽象,虚,静态,密封等方法的标记.Signature就是方法的签名,由返回值和参数组成。RVA是方法体在Dll文件中的相对地址,也就是方法的代码所在的相对地址。

除此之外,还有方法参数的起始位置.在类型那一篇中,我们讨论过类型中包含了字段起始位置,和方法的起始位置,那么这里的参数起始位置和前面二者的原理是相同的,用来查找方法的参数.来看看方法参数的元数据表吧

探索CLR原理系列(3):方法元数据和IL(适合老鸟,新人勿沉迷其中)

在上面的IL代码中Add方法的参数分别为一个值传递,一个引用传递,那么他们在IL中被标示为Byref,并且Out参数的Flags被标注为out,而Ref则与值传递参数的Flags没有任何区别,都为None,那么CLR在运行时如何判断呢?关键在于IL代码中,我们可以看到一个类似于C++中的去地址操作符&.
.methodpublichidebysigstaticint32Add(int32a,

int32&b)
这就是引用传递和值传递的区别了,Out参数也一样,只不过在参数原数据表中的Flags中标记了一个Out。那么参数原数据表中Flags的另一个标记Opt是什么意思呢??呵呵,大家可以自己试验一下,定义一个可变参数params inta 看看。

Sequence是什么意思?他代表参数的位置,也就是第几个参数。所以Sequence的值不能大于方法的参数个数。

也许大家会问,那么参数的类型呢,怎么不在参数元数据表中?这个秘密就是在方法的签名中。。。懂了吗?


总结

我们了解了方法是由元数据和IL代码组成。元数据中包括了方法的签名,方法的参数,以及参数的描述,IL代码中包含了方法的执行逻辑 。每个方法在方法元数据表中都有一条记录,该记录包括方法在编译时的标识Token,以及方法的可见性,契约的特征Flags,还有方法的参数和返回值组成的签名,乃至引用了方法的每一个参数的特征,由参数元数据表来记录这些信息。


下一篇我们来聊聊方法在类型继承中的情况。。。

原文链接: https://www.cnblogs.com/hhhgame/archive/2011/10/31/2642863.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/35444

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月8日 下午12:19
下一篇 2023年2月8日 下午12:20

相关推荐