类的扩充
在ILAsm中,和在Visual Basic和C#中,一个类的所有的成员、特性和内嵌类都在这个类的词法范围内声明。然而,ILAsm允许你重新打开一个已经关闭的类范围并定义额外的项:
}
// Later in the source, possibly in another source file
.class X {
// More items defined
}
这种对类范围的重新打开被称为类的扩充。一个类可以遍及源代码被扩充任意多次,而且扩充模块可以位于不同的源文件中。下面的简单安全性规则管理着类的扩充:
类必须完全定义在模块中——换句话说,你不可以扩充一个定义在别的什么地方的类。(难道这样不好么?再见吧,安全——完美的状态!)
Class标记,extends子句,以及implements子句必须完全在词法上定义在类的首次打开的范围内,因为这些特性在扩充部分中是被忽略的。
扩充部分不可以包括重复项的声明。如果你声明int32字段X在一个片断中,然后又在另一个片断中声明它,ILAsm编译器将不会感激你可能成心要有两个相同的字段,并将读取它作为在一个相同的类中定义两个同样字段的一个尝试,而这是不允许的。
以1.0版本和1.1版本写一个ILAsm程序,一个好的策略是使用前向类声明(forwarder class declaration),在第一章解释过。这个策略允许你声明当前模块的所有类,包括内嵌类,不带任何成员和特性,并且在扩充部分中定义成员和特性。这种方式,IL编译器在任意类型被引用之前就充分了解模块的类型声明结构。到本地声明的类型被引用的时候,它们已经全都被定义了并且有相应的TypeDef元数据记录。
可是,在ILAsm的2.0版本中不再需要前向类声明了。在2.0版本中,I编译器隐式地声明一个类无论何时这个类被提到,作为一个声明或者作为一个引用。当然,这个隐式声明在一个引用上的类仅仅是一个哑元——占位符。当这个类的声明{.class … { … }}在源代码中被遇到的时候,传递一个哑元到“真实”的类声明。如果所有的编辑被分析了,并且这里仍然留有“哑元”,编辑就会失败。
扩充部分并没有被显示地数字化,而且这个类是根据扩充部分在源代码中的顺序来扩充的。这意味着如果你交换扩充部分,这将依次影响类的布局,那么类的项声明顺序将会改变。
可是,如果你想保留类声明的顺序或者你考虑以某个特定的顺序发布这个类的声明,你可以使用指令.typelist:
.typelist { FirstClass SecondClass ThirdClass ...}
.Typelist指令最好放在第一个源文件的顶部,如果存在的话,甚至在清单声明之前而在.mscorlib指令之后。这样放置的原因是明显的:IL编译器需要立刻知道你是否正在编译Mscorlib.dll或者其它别的什么,而且清单声明可能有自定义特性,或者其它类的引用,这可能混合类声明的原有顺序。
清单声明,在第6章描述过,加上前向类声明(1.0和1.1版本)或.Typelist指令(2.0版本),看上去像一个程序头,因此我不会责备你,如果你把它们放在一个单独的源文件中。只是不要忘记,当你编译程序集时,这个文件必须优先位于源文件之中。
元数据验证规则小结
回想类型相关的元数据表(除去那些被涉及到的泛型,这将在第11章讨论)包括了TypeDef、TypeRef、InterfaceImpl、NestedClass和ClassLayout。这些表的记录包括了下面的项:
TypeDef表包括了Flags、Name、Namespace、Extends、FieldList和MethodList项。
TypeRef表包括了ResolutionScope、Name和Namespace项。
InterfaceImpl表包括了Class和Interface项。
NestedClass表包括了NestedClass和EnclosingClass项。
ClassLayout表包括了PackingSize、ClassSize和Parent项。
TypeDef表验证规则
Flags项只可以具有那些定义在CorHdr.h中的CorTypeAttr枚举的位设置。除了tdFowarder这个为导出类型(验证标记:0x00173DBF)所保留的标记。
[runtime] Flags项不可以同时具有sequential和explicit位设置。
[runtime] Flags项不可以同时具有unicode和autochar位设置。
如果在Flags项中设置了rtspecialname标记,Name字段就必须被设置为_Deteled*,反之亦然。
[runtime] 如果在Flags项中设置了0x00040000位,DeclSecurity记录或名为SuppressUnmanagedCodeSecurityAttribute的自定义特性必须与TypeDef联合,反之亦然。
[runtime] 如果在Flags项中设置了interface标记,就必须设置abstract。
[runtime] 如果在Flags项中设置了interface标记,就必须设置sealed。
[runtime] 如果在Flags项中设置了interface标记,TypeDef必须没有实例字段。
[runtime] 如果在Flags项中设置了interface标记,TypeDef的所有实例方法必须是抽象的。
[runtime] 如果TypeDef的可见性标记被设置为nested private,nested family,nested assembly,nested famorassem或nested famandassem,TypeDef必须在NestedClass元数据表一笔记录的NestedClass项中被引用,反之亦然。
Name字段必须引用一个#Strings流中的非空字符串。
由Name和Namespace引用的字符串的联合长度不可以超过1023字节。
TypeDef表不可以包括具有相同全名(命名空间加上名称)的重复记录,除非TypeDef是内嵌的或者被删除的。
[runtime]对于带有interface标记设置的TypeDef 和Mscorlib程序集的System.Object,Extends项必须是零。(这句话貌似原文笔误)
[runtime] 所有其它TypeDef的Extends项必须保存一个指向TypeDef、TypeRef或TypeSpec表的有效引用,并且这个引用必须指向一个非密闭的类。
[runtime] Extends项不可以指向类型本身或者该类型的子孙(继承循环)。
[runtime] FieldList项必须是零或者保存对Field表的一个有效的引用。
[runtime] MethodList项必须是零或者保存对Method表的一个有效的引用。
Enumeration-Specific验证规则
如果TypeDef是一个枚举——就是说,如果Extends项保存了对[mscorlib]System.Enum的引用——适用于下面额外的规则:
[runtime] interface、abstract、sequential、和explicit标记不可以在Flag项中被设置。
Sealed标记必须在Flag项中被设置。
TypeDef不可以有方法、事件或属性。
TypeDef不可以实现接口——就是说,它不可以在InterfaceImpl表的任何记录中的Class项被引用。
[runtime] TypeDef必须至少有一个整型或布尔或字符类型的实例字段。
[runtime] TypeDef的所有静态字段必须是文本化的。
TypeDef的静态字段的类型必须是当前的TypeDef本身。
TypeRef表验证规则
[runtime] ResolutionScope项必须保存0或者一个指向AssemblyRef、ModuleRef、Module或TypeRef表的有效引用。在最后一种情形中,TypeRef指向一个内嵌在另一个类型中的类型(一个内嵌的TypeRef)。
如果ResolutionScope项为0,这个程序集的主模块的ExportedType表必须包括一笔记录,它的TypeName和TypeNamespace项相应地与TypeRef记录Name和Namespace项匹配。
[runtime]Name项必须引用#Strings流中一个非空的字符串。
InterfaceImpt表验证规则
将Class项设置为0意味着一笔已删除的InterfaceImpt记录。然而,如果Class项是非0的,那么就适用下面的规则:
[runtime] Class项必须保持对TypeDef表的一个有效的引用。
[runtime] Interface项必须保持对TypeDef或TypeRef表的一个有效的引用。
如果Interface字段引用了TypeDef表,相应的TypeDef记录必须在Flags项设置interface标记。
这个表不可以包括带有相同Class和Interface项的重复记录
NestedClass表验证规则
[runtime] NestedClass项必须保持对TypeDef表的一个有效的引用。
[runtime] EnclosingClass项必须保持对TypeDef表的一个有效的引用,一种不同于由NestedClass项保持的引用。
这个表不可以包括带有相同EnclosingClass和NestedClass项的记录,这些项引用了带有匹配名称的TypeDef记录——换句话说,一个内嵌类在它的外包类中必须有一个唯一的名称。
这个表不可以包括一组形成循环嵌套模式的记录——例如,A内嵌在B中,B内嵌在C中,C内嵌在A中。
ClassLayout表验证规则
将Parent项设置为0意味着一笔已删除的ClassLayout记录。然而,如果Parent项是非0的,那么就适用下面的规则:
Parent项必须保持对TypeDef表的一个有效的引用,而且被引用的TypeDef记录必须将Flags位设置为explicit或sequential,并且不可以设置interface位。
[runtime] PackingSize项必须设置为0或者1到128范围内2的指数幂。
这个表不可以包括带有相同Parent项的重复记录。
terminology 术语
inherently 本质上
all of a sudden 突然
in a general sense笼统地
engage in 使从事于, 参加
元数据中的entry翻译成项比较好
Resolution Scope 解析范围
lookup table 查找表
alphabetic character 文字符号
economy 节省
marshal 封送
at will 随意
granularity 粒度
under construction
bear in mind 记住
stem from the fact 来源于
namely 也就是
trivial 平凡的
forward slash ---> /
back slash ------> "
in this regard 在这点上
one that 一种