(翻译)《Expert .NET 2.0 IL Assembler》 第六章 模块和程序集(三)

返回目录

  

File元数据表和声明

File元数据表描述了在当前模块宗引用到的同一个程序集的其它文件。在单模块程序集中,这个表是空的(除非你想要详细指明非托管DLL作为你的部属的一部分,正如在本章前面所描述的)。这个表有如下的列结构:

       Flags4字节宽位域):描述文件特征的二进制标记。为了将来使用,这个入口总是保留的,当前定义的唯一标记是ContainNoMetaData0x00000001)。这个标记指出正被讨论的文件鄙视一个托管的PE文件而是一个纯的资源文件。

Name#String流中的偏移量): 文件名,与在ModuleModuleRef中的名称遵守同样的规则。这是在元数据模块中唯一发生的数据复制:File名称与使用在ModuleRef中的名称相匹配——这个ModuleRefFile记录是成对的。然而,由于在这两个记录中的名称不是天然的字符串而是在字符串堆中的偏移量,这个字符串数据可能不是严格意义上可复制的;替代的,这两笔记录可能引用堆中同样的字符串。这并不意味着不存在数据复制:偏移量确实被复制了。

HashValue#Blob流中的偏移量): 这个blob表示了文件的哈希值,用于鉴别多文件程序集中的文件。即使在一个强名称的程序集中,强签名只位于主模块中并只覆盖到主模块。程序集中的非主模块不能通过它们的哈希值来鉴别。

ILAsm中文件的声明就像下面这样:

.file <flag> <name>  .hash = ( <bytes> )
where <flag> ::=
<none>      
// The file is a managed PE file
| nometadata    // The file is a pure resource file

如果这个哈希值没有被显示地指定,IL编译器就会找到这个名称的文件并通过在Assembly声明中指定的哈希算法计算这个哈希值。如果这个文件在编译期间是不可用的,相应File记录的HashValue入口就被设置为0

       File声明也可以携带.entrypoint指令,正如在这个示例所示:

Code

     File声明的这种分类只能发生在多模块程序集的主模块中,并只能发生于入口点方法定义在这个程序集的非主模块中的时候。File声明的这个子句并不影响元数据,但是它把相应的文件符号放入CLR头的EntryPointToken入口中。参见第4章获取关于EntryPointTokenCLR头的细节。

       一个程序集的主模块,尤其是一个可运行的应用程序(EXE),必须在CLR头的EntryPointToken字段中具有一个有效的符号;而这个符号必须是一个Method符号——如果入口点方法定义在主模块中;或者是一个File符号。在后面这种情形中,加载器加载相关的模块并检查它的CLR头,它必须在EntryPointToken字段包括一个有效的Method符号。

 

 

托管资源元数据和声明

资源是不可执行的数据,作为应用程序的一部分进行逻辑部署。这些数据可以接受任何数量的形式如字符串、图片、持久化对象等等。正如第4章描述的,资源可以是托管或非托管的(特定于平台的)。这两种类型的资源具有不同的格式并通过相应的托管的和非托管的API被访问。

必须要经常为应用程序定制不同的文化。文化是一组基于用户语言、次语言和文化约定的优先权。在.NET Framework中,文化由来自.NET Framework类库的CultureInfo类来描述。文化用于定制诸如日期格式和数字、分类字符串等等的操作。

你可能还需要为不同的国家或区域定制一个应用程序。区域为这个世界的一个特定的国家或区域定义了一组标准。在.NET Framework中,类库描述了一个使用RegionInfo类的区域。这个区域是用于定制诸如格式化货币符号的操作。

       应用程序的本地化是将应用程序执行体代码和由特定文化定制的应用程序资源连接起来的过程。虽然文化和区域共同组成了本地化,本地化并没有将定制应用程序连接到一个特定的区段。.NET FrameworkCLR不需要支持组件元数据的本地化,代替地单独依赖于这项任务的托管资源。

       .NET Framework使用了集散模型来打包和部署资源。Hub是主编译集,它包括了一个用于单独的文化的可执行体代码和资源(称为中性文化)。中性文化对于应用程序而言是备用的区域设置。每个Spoke为一个单独的文化连接一个附属程序集(satellite assembly),这个程序集包括了一种单一文化的资源。附属程序集不包括代码。

包包译注:“Hub and Spoke”模型,是因为中央数据库汇集了来自各业务处理系统的数据,同时也负责向各从属数据集市提供信息,看上去象一个Hub (集线器)一样。而业务人员在进行数据分析与信息访问时将根据需要连接到不同的数据集市,这种交叉复杂的连接看上去就象Spoke(车轮辐条)一样。由于这样的关系,著名评估机构Gartner Group把这种结构的数据仓库形象地称为“Hub and Spoke Data Warehouse”

这种模型的优点是明显的。首先,在应用程序部署后,用于新样式的资源可以呈增长型地添加。其次,应用程序需要只加载那些包括了用于特定运行所需要的资源的附属程序集

在一个程序集中使用或由程序集暴露的资源,可以位于下面的位置中:

l         在同样的程序集的独立的资源文件中。每个资源文件能够包括一个或多个资源。这样文件的元数据描述符携带了nometadata标记。

l         内嵌在同一个程序集中的托管模块。

l         在另一个(外部的)程序集中。

       资源数据不是直接使用的,也不是由部署子系统或加载器验证的,因此它可以是任意类型的。

       所有内嵌在一个托管的PE文件中的资源数据,位于.text区段中的一个连续的块。在CLR头中的Resource数据目录提供了RVA和内嵌的托管资源的大小。每个独立的资源都在一个4字节无符号整数的后面,这个保存了资源的字节长度。图6-3显示了内嵌的托管资源的布局。

 

6-3 内嵌的托管资源的布局

 

ManifestResource元数据表,描述了托管资源,具有下列的列结构:

       Offset4位无符号整数):资源在托管资源片断的位置,是CLR头的Resource数据目录指定的。这不是一个RVA;而是在托管资源片断中的一个偏移量。

       Flags4位宽位域):二进制标记,以指明托管资源是public(可以从程序集的外部访问到),还是private(只可以在当前的程序集中访问))。

       Name#String流中的偏移量):资源的非空名称,在程序集中是唯一的。

       ImplementationImplementation类型的编码符号):如果资源位于另一个程序集中,就是相应的AssemblyRef记录的符号,或者,如果资源位于当前程序集的另一个文件中,就是相应的File记录。如果资源内嵌在当前模块中,这个入口就被设置为0。如果这个资源是被另一个程序集导入的,就不需要指定偏移量了;加载器将会忽略它。

       用于声明一个托管资源的ILAsm语法如下:

.mresource <flag> <name> { <mResourceDecl>* }
where <flag> ::= 
public | private and <mResourceDecl> ::=
.assembly extern <alias>  //  Resource is imported from another assembly
.file <name> at <int32// Resource resides in another file of this assembly; <int32> is the offset
| <customAttrDecl> //  Define custom attribute for this resource

       默认的标记值是private

       在托管资源声明的上下文中,指令.assembly extern.file指向资源的Implementation入口,而且它们是不共存的。如果Implementation在其被声明之前引用了AssemblyRefFileILAsm编译器就会诊断到一个错误。

       如果Implementation入口是空的,资源就被假定为内嵌在当前的模块中。在这种情形中,IL编译器创建了PE文件,根据资源的名称从这个文件中加载资源,并将其写入PE文件的.text区段,自动设置ManifestResource记录的Offset入口。当IL反编译器将一个PE文件反编译为一个文本文件时,这个内嵌的托管资源就被保存到以这些资源命名的二进制文件中,当PE文件需要被重编译的时候,这将允许IL编译器容易地得到它们。

       这里有一个小圈套:托管资源的名称可能包括不适合用于文件名的字符。在这种情况中,托管资源不能被保存为它的真实名称,另一方面,你不可以改变资源的名称,因为资源通过这些在应用程序中的名称被寻址。为了处理这种情形,ILAsm2.0版本提供了托管资源的别名——类似于被应用程序集的别名。

.mresource <flag> <name> as <filename> { <mResourceDecl>* }

前面的指令提示IL编译器从文件<filename>加载资源并创建带有名称<name>的相应的ManifestResource元数据记录。ILAsm2.0版本,在保存一个托管资源到文件的时候,分析这些资源的名称,并且如果它找到了冒号、分号或反斜线字符,它就会为这个资源创建一个别名,用!@&$代替这些字符。然后这个资源就可以存储在别名文件中了。

       ILAsm不提供任何语言结构对托管资源进行寻址,因为IL欠缺这样做的方法。由.NET Framework类库提供的托管的API——尤其是System.ResourceManager类——用于加载并操作托管资源。

 

posted @ 2008-09-02 16:27  包建强  Views(711)  Comments(0Edit  收藏  举报