在上一篇中我们着重介绍到了清单(Manifest),现在继续进行元数据的介绍:
通过主模块中的清单,我们已经可以
(1)通过FileDef找到程序集内非主模块和资源文件的信息:
(2)通过ManifestResourceDef可以找到整个程序集(Assembly)内部的所有类型和资源的位置等信息;
(3)通过ExportedTypeDef可以找到所有程序集内在程序集外可见的类型的位置信息。(从这里我猜测,ManifestResourceDef是用来程序集内部调用时使用的,ExportedTypeDef是外部使用的,分开有利于效率的提高)。
现在找到类型了,我们要使用类型内部具体的属性、字段、方法等,怎么办呢?现在元数据就为我们提供了这些信息,(感觉清单和元数据就像是程序集内IL代码的分层索引表)
元数据主要是用来存放模块内类型详细信息的,这些信息以表的形式保存着,主要包含三种类型的表:
1、定义表:
每一个定义表都包含一种模块内特定元素的信息,下面对定义表中的几个比较常见的表介绍一下:
(1)、ModuleDef这个表定义了当前模块的信息,其中包含一个带扩展但不带路径的文件名,该表只有一项;
(2)、TypeDef该表为模块中的每一个类型保存一个项,包含该类型的名称、基类型、类型的标志(Public、internal、sealed等)以及该类型成员在MethodDef、FieldDef、PropertyDef、EventDef等元数据表中的索引
(3)、MethodDef为每一个方法保存一个项,保存方法的名称(并非直接保存字符串名称而是一个引用,引用#String堆内的字符串名称,#String堆用来保存字符串属于元数据的一部分)、标志(public、abstract、sealed等)、一个指出方法在IL代码中的位置的偏移量的值,以及一个指向方法签名的引用(方法签名以一种二进制的格式保存在#Blob堆中,#Blob堆用来保存二进制信息,属于元数据的一部分)。
(4)、还包含一些其他的表FieldDef(字段信息的表)、PropertyDef(属性信息的表)、EventDef(事件信息的表)等,据说这些表示标准的,而且每个表都是用一个唯一字节来编号,还据说,.NET中所有的MethodDef表都有一个表编号为6,但是我不知道这个6在哪?
··定义表主要是对类内部的类型及类型的方法、字段、属性、事件等成员提供了索引作用的信息列表。
2、引用表:保存了模块内所引用的元素的信息。
(1)、AssemblyRef为本模块引用的每一个程序集保存一项,每一项都包含组成强名称的4个组成部分:程序集名称(不带路径和扩展名)、版本号、区域设置、公钥记号(Public key token)(若没有则为空);
(2)、ModuleRef为本模块所引用的属于本模块所在程序集的每一个模块保存一个项(保存项的模块内至少包含一个被本模块所引用的元素),包含模块的名称一起扩展名。
(3)、TypeRef为被模块引用的每一个类型保存一个项。每一项包含类型的名称和一个类型位置的引用。这里的引用将分别指向AssemblyRef(如果引用的类型定义在其他程序集)、ModuleRef(如果引用的类型就定义同一程序集内的其他模块)、TypeRef本身(如果该类型嵌套在另一个类型中?是否是在本模块内的呢?自己感觉应该是)
(4)、MemberRef这个表为本模块内引用的每一个成员(方法、字段或者属性)保存一项,每项包含该成员的名称、签名、以及一个TypeRef的引用。
据说引用表的定义也符合标准的,并使用一个字节来编号,据说所有的.NET模块的MemberRef表均用数字10来标识。
3、指针表:
不太明白是做什么用的,编译器可以通过指针表来引用位置的代码(有点像C++中的声明),只要改变代码中的元素声明顺序就可以减少指针表的内容,书上列出了几种指针表:MethodPtr、ParamPtr、FieldPtr。
4、堆:
除了上面在定义表中介绍的#String和#Blob外,还有两种:
#US(User String)包含直接定义在代码中的字符串(具体值得是什么?很不明确呢)。
#GUID:这个很明确用于保存程序中定义和使用的GUID。
5、#~堆:
相关文档有时会涉及一个名为#~的堆,这个特殊的对实际上包含所有的元数据表,如果是主模块还将包含清单。
元数据类型对于.NET架构来说非常重要,IL代码中的元数据符号会应用这些元数据信息。.Net Attribute之一概念以及反射机制也用到了类型元数据。