2.5 将模块合并成一个程序集 - 2

当你指定下面任何一个命令行开关时, C#编译器都产生一个程序集: /t[arget]:exe, /t[arget]:winexe, 或者/t[arget]:library. 这些开关都会使得编译器产生单独的PE文件, 其包含着manifest metadata, 产生的文件或者CUI可执行程序, 或者是GUI可执行程序, 或者是一个DLL.

除了这些开关, C#编译器支持/t[arget]:module开关, 这个开关告诉编译器产生一个不包含manifest metadata表的PE文件, 产生的PE文件总是一个DLL PE文件, 这个文件必须被加入到一个程序集中, CLR 才能访问它中的类型. 当你使用/t:module开关时, C#编译器默认情况下会在输出文件中增加一个.netmodule扩展名.

重要: 不幸的是, 微软的Visual Studio集成开发环境(IDE)不支持创建多文件程序集, 如果你想创建多文件程序集, 你必须求助于命令行工具.

有很多方法可以给一个程序集增加模块, 如果你使用C#编译器来构建带有manifestPE文件, 你可以使用/addmodule开关. 为了理解如何构建一个多文件程序集, 让我们假设你有两个源代码文件:

Ÿ   RUT.cs: 包含很少使用的类型

Ÿ   FUT.cs: 包含经常使用的类型

让我们把很少使用的类型编译为他们自己的模块, 使得程序集的用户不需要部署这个模块, 如果用户不需要访问这个很少使用的类型.

csc /t:module RUT.cs

这行命令使C#编译器创建一个RUT.netmodule文件, 这个文件是一个标准的DLL PE文件, 但是, 仅仅通过这一个文件, CLR还不能载入它.

下面让我们编译经常使用的类型为他们自己的模块, 我们将使得这个模块保存程序集的manifest, 因为这个模块中的类型是经常被使用的. 实际上, 因为这个模块将会代表整个程序集,  我将改变输出文件的名字为JeffTypes.dll, 而不是默认的FUT.dll.

csc /out:JeffTypes.dll /t:library /addmodule:RUT.netmodule FUT.cs

这行命令告诉C#编译器编译FUT.cs文件并产生JeffTypes.dll文件. 因为指定了/t:library, 一个包含manifest metadata表的DLL PE文件被放到JeffTypes.dll文件中. /addmodule:RUT.netmodule开关告诉编译器RUT.netmodule是一个文件, 这个文件应该被认为是程序集的一部分. 特别地, /addmodule开关告诉编译器在FileDef manifest metadata表中增加一个文件, RUT.netmodule的公开暴露的类型加入到ExportedTypesDef manifest metadata表中.

当编译器完成所有这些操作时, 在图2-1中显示了创建的两个文件, 在右边的模块包含了manifest.

2-1 多文件程序集: 包含两个托管模块, 其中一个包含着manifest

RUT.netmodule文件包含着IL代码(通过编译RUT.cs所产生的), 这个文件也包含着metadata, 其描述了RUT.cs中定义的类型, 方法, 字段, 属性, 事件, . 这个metadata标也描述了RUT.cs中引用的类型, 方法, . JeffTypes.dll是一个单独的文件, 类似于RUT.netmodule, 这个文件包含着IL代码(编译FUT.cs所产生的), 这个文件包含着类似的定义和引用metadata. 然而, JeffTypes.dll还包含着额外的manifest metadata, 使得JeffTypes.dll是一个程序集. 这个额外的manifest metadata表描述了组成程序集的所有的文件(JeffTypes.dll文件自身和RUT.netmodule文件). manifest metadata表也包含了所有JeffTypes.dllRUT.netmodule公开暴露的类型.

注意: 实际上, manifest metadata表中没有包含着manifestPE文件中暴露的类型, 这个优化的目的是为了减小PE文件中manifest信息的字节数量, 所以陈述 manifest metadata表也包含了所有JeffTypes.dllRUT.netmodule公开暴露的类型不是100%的准确. 然而这个陈述准确地反映了manifest在逻辑上所暴露的内容.

当构建了JeffTypes.dll程序集, 你可以使用IlDasm.exe来检查metadatamanifest表来验证程序集文件真的包含着RUT.netmodule文件中的类型. 下面是FileDefExportedTypesDef metadata表的样子:

File #1 (26000001)

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

Token: 0x26000001

Name : RUT.netmodule

HashValue Blob : e6 e6 df 62 2c al 2c 59 97 65 0f 21 44 10 15 96 f2 7e db c2

Flags : [ContainsMetaData] (00000000)

ExportedType #1 (27000001)

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

Token: 0x27000001

Name: ARarelyUsedType

Implementation token: 0x26000001

TypeDef token: 0x02000002

Flags : [Public] [AutoLayout] [Class] [Sealed] [AnsiClass]

[BeforeFieldlnit](00100101)

从这个例子可以看出, RUT.netmodule是一个文件, 是程序集的一部分, token0x26000001. ExportedTypesDef表中, 可以看到有一个公开暴露的类型ARarelyUsedType, 这个类型的实现token0x26000001, 这表明这个类型的IL代码包含在RUT.netmodule文件中.

注意: 出于好奇, metadata token是一个4字节的数值, 高位字节表示token的类型(0x01=TypeRef, 0x02=TypeDef, 0x23=AssemblyRef, 0x26=FileRef, 0x27=ExportedType). 完整的列表可以参考CorHdr.hCorTokenType枚举的类型, 这个文件是.NET Framework SDK中包含的文件. 三个低位字节简单地表明了在对应的metadata表中的行. 例如, 实现token 0x000001引用了FileRef表的第一行. 对大多数表来说, 行是从1开始计数的, 而不是0. 对于TypeDef, 行是从2开始计数的.

任何client代码要使用JeffTypes.dll程序集的类型, 它必须用/r[eference]: JeffTypes.dll编译器开关来构建. 这个开关告诉编译器载入JeffTypes.dll程序集以及在它的FileDef表中列出的所有文件(当搜索外部类型时). 编译器需要所有的程序集文件, 如果你删除了RUT.netmodule文件, C#编译器将会产生如下错误: "fatal error CS0009: Metadata file 'C:/JeffTypes.dll could not be opened'Error importing module 'RUT.netmodule' of assembly 'C:/JeffTypes.dll'The system cannot find the file specified'". 这意味着为了构建一个新的程序集, 被引用的程序集中的所有文件都必须存在.

client代码执行时, 它会调用相应的函数. 如果函数是第一次调用, 那么CLR会检查函数引用的参数类型, 返回值类型, 以及局部变量的类型. 然后CLR尝试载入被引用的程序集文件(包含着manifest的程序集). 如果正在被访问的类型在这个文件中, 那么CLR执行内部的薄记, 允许使用类型. 如果manifest表明被引用的类型在一个不同的文件中, CLR尝试载入必要的文件, 执行内部的, 并允许访问类型. 只有在一个方法引用一个类型,并且这个类型是在一个没有被载入的程序集中时, CLR才载入相应的程序集文件. 这意味着, 运行一个应用程序, 被引用程序集中的所有文件不必都存在.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值