前面讨论的Program.exe文件不止是一个带有metadata的PE文件, 它也是一个程序集(assembly). 程序集是一个或多个包含类型定义和资源的文件的集合体. 组成一个程序集的文件被放到manifest中, manifest是另一组metadata表, 其包括着组成程序集的文件的名字, 他们也描述着程序集的版本, 语言, 发行者, 公开暴露的类型, 以及组成程序集的所有文件.
CLR在程序集进行操作, 也就是说, CLR总会先载入包含manifest表的文件, 然后通过manifest来获得程序集中的其他文件. 下面是你应该记住的有关程序集的特性:
程序集定义了可重用的类型
程序集用版本号来标记
程序集可以有与之关联的安全性信息
程序集中包含的文件不必有这些属性--除了包含manifest metadata表的文件.
对于软件包, 版本, 安全性, 类型, 你必须把它们放在模块中, 在多数情况中, 一个程序集只包含一个文件, 例如上面的Program.exe例子, 然而, 一个程序可以包含多个文件: 一些包含metadata的PE文件, 和一些资源文件, 例如.gif或.jpg文件. 你可以把一个程序集想象成一个逻辑上的EXE或者DLL.
我认为很多人会问为什么微软要引入程序集这个新的概念, 其原因是程序集能让你将逻辑上的类型重用和物理上的类型重用分离开. 例如, 程序集可以由多个类型组成, 你可以把频繁使用的类型放到一个文件中, 不太常用的类型放到另一个文件中, 如果你的程序集是通过Internet发布的, 如果客户端从不访问不太常用的类型, 那么它们所在的文件永远都不需要被下载到客户端. 例如, 一个独立的软件厂商(ISV)专注于UI控件, 它可能会选择在单独的模块中实现Active Accessibility类型(来满足微软的Logo要求). 只有需要这个额外Accessibility功能的用户才需要下载这个模块.
你可以通过在应用程序的配置文件中指定codeBase元素(第三章会讨论), 来配置应用程序来下载程序集文件. codeBase元素给出了一个URL来指向程序集文件所在的位置. 当企图载入一个程序集文件时, CLR获得codeBase元素的URL并检查机器的download cache来看文件是否存在, 如果文件存在, 那么就载入文件, 如果文件不在cache中, 那么CLR就会下载文件并放到cache中, 如果在URL所在的位置没找到哦文件, 那么会抛出FileNotFoundException.
我发现使用多文件的程序集的三个原因:
你可以在把你的不同类型放到不同的文件中, 这可以让文件逐个地下载, 例如上面提到的Internet下载的场合. 类型分离到不同的文件也能为你要购买的应用程序部分地打包, 部署.
你可以在你的程序集中添加资源或数据文件. 例如, 你可能有一个计算某种保险信息的计算器, 这个类型可能需要访问一些保险统计表格来完成它的计算. 不用将保险统计表格嵌入到你的源代码中, 你可以使用一个工具(例如Assembly Linker, AL.exe, 后面会讨论)使得数据文件成为程序集的一部分. 通过这种方式, 这个数据文件可以是任何类型的格式, 例如文本文件, Office Excel表格, Offfice Word表格, 或者其他你想用的任何数据, 只要你的应用程序知道如何解析文件的内容.
你可以创建包含用不同语言实现类型的程序集, 例如, 你可以用C#实现一些类型, VB实现另外一些类型, 以及其他语言实现其他类型. 当你编译用C#编写的源代码中的类型时, 编译器会产生一个模块. 当你编译用VB编写的源代码中的类型时, 编译器会产生另外一个模块. 然后你可以使用一个工具来组合所有的模块, 来产生一个程序集. 对于使用程序集的开发者来说, 程序集仅仅像是包含一群类型而已, 开发者不需要知道程序集使用了不同的语言. 通过这种方式, 如果你愿意, 你可以对每个模块运行ILDasm.exe来获得IL源代码文件, 然后你可以运行ILAsm.exe, 并传入所有的IL源代码文件, 它会产生一个单独的包含所有类型的文件, 这个技术需要你的源代码编译器产生IL-only代码.
重要: 总结一下, 一个程序集是一个重用, 版本控制, 和安全控制的单元. 它允许你把多个类型, 资源放到不同的文件中, 使得你和使用程序集的客户决定打包和部署哪些文件. 一旦CLR载入包含manifest的文件, 它可以决定程序集的那些文件包含着类型和资源, 使用程序集的人只需要知道包含manifest的文件名, 文件分割被抽象化出来, 将来可以改变分割的方式而不影响应用程序的行为.
如果你有多个类型, 它们可以共享一个版本号和安全设置, 那么推荐你把所有的类型放到一个单独的文件中, 而不是把它们分散到不同的文件, 更不要放到不同的程序集, 这是因为性能方面的原因, 载入一个文件/程序集要消耗CLR和Windows的时间来找到程序集, 载入程序集, 和对其进行初始化. 载入越少的文件/程序集越好, 因为载入较少的程序集会帮助你减少工作集, 减少进程地址空间的碎片. 最后, 在处理大文件时, nGen.exe能执行更好的优化.
为了构建一个程序集, 你必须选择一个PE文件来包含manifest, 或者你可以创建一个单独的文件来只包含manifest, 表2-3给出了manifest metadata表:
Manifest metadata表 | 描述 |
AssemblyDef | 如果这个模块标识一个程序集, 那么只包含一个条目. 这个条目包括程序集的名称(没有路径和扩展名), 版本(主版本号, 次版本号, build版本号, 修改版本号), 语言, 标志, hash算法, 发行者的公开密钥(可以是null). |
FileDef | 对每个PE和资源文件(除了包含manifest的文件, 因为它在AssemblyDef表中)都包含一个条目, 条目包括文件名和扩展名(没有路径), hash值,标志. 如果这个程序集只包含一个文件, 那么FileDef表没有条目. |
ManifestResourceDef | 对每个资源都包含一个条目, 条目包括资源的名字, 标志(如果对外部的程序集可见则为public, 否则为private), 以及指向FileDef表格的一个索引. 如果资源不是一个独立的文件(例如.jpg或.gif文件), 那么资源是一个包含在PE文件中的流. 对于一个嵌入的资源, 条目还包含了一个偏移量来指示资源流在PE文件中的起始位置. |
ExportedTypesDef | 对每个公开的类型都包含一个条目, 条目包括类型的名字, 指向FileDef表的索引(表明那个文件实现了这个类型), 以及一个指向TypeDef表格的索引. 注意: 为了节约空间, 包含着manifest的文件中的导出类型不需要在这个表中重复了, 因为类型的信息可以在metadata中的TypeDef表中得到. |
Manifest为程序集的客户与程序集的分割细节之间提供了间接一级, 使得程序集是自描述的. 记住包含manifest的文件具有metadata信息, 这些信息表示那些文件是程序集的一部分, 但是单独的文件没有metadata信息.
注意: 包含manifest的程序集文件还有一个AssemblyRef表格, 这个表格为每个被该程序集引用的引用的其它程序集都包含一个条目, 这可以让工具打开一个程序集的manifest, 查看它引用的程序集, 而不必打开程序集的其他文件. 在AssemblyRef表格中的条目使得程序集是自描述的.