11.2 .NET程序集的格式
我们已经知道了.NET程序集所带来的好处,现在进一步深入程序集的内部结构。从结构上看,一个.NET程序集(*.dll或者*.exe)包含以下几个部分:
l Win32文件首部
l CLR文件首部
l CIL代码
l 类型元数据
l 程序集清单
l 可选的嵌入资源
通常头两个部分(Win32文件首部和CLR文件首部)容易被忽视,但其实它们是值得简要了解一下的。下面来逐个讲解。
11.2.1 Win32文件首部
Win32文件首部使程序集可以被Windows系列操作系统加载和操作。这些首部信息标识了应用程序将以什么类型(是基于控制台、基于图形界面还是*.dll代码库)驻留于Windows操作系统中。使用dumpbin.exe工具(通过.NET Framework 2.0 SDK命令提示符)结合/headers标记打开一个.NET程序集,我们可以浏览该程序集的Win32文件首部信息。图11-1展示了CarLibrary.dll程序集的部分Win32首部信息,该程序集将在本章的后面创建。
11.2.2 CLR文件首部
为了驻留于CLR中,所有的.NET文件都必须含有CLR首部数据块。简单地讲,CLR文件首部定义了多个标记,它们使得运行库可以了解到托管文件的布局。例如,这些标记标识了文件中元数据和资源的位置、程序集构建的运行库版本、(可选的)公钥值等。使用dumpbin.exe工具结合/clrheader标记打开一个.NET程序集,我们就可以浏览该程序集内部的CLR首部信息,如图11-2所示。

图11-2 一个程序集CLR文件首部信息
CLR头数据以非托管C语言形式的结构体(IMAGE_COR20_HEADER)方式被定义在名为corhdr.h的基于C的头文件中。如果对此感兴趣,请看下面结构体的布局。
// CLR 2.0 首部结构。
typedef struct IMAGE_COR20_HEADER
{
// 首部版本(Header versioning)。
ULONG cb;
USHORT MajorRuntimeVersion;
USHORT MinorRuntimeVersion;
// 符号表和启动信息。
IMAGE_DATA_DIRECTORY MetaData;
ULONG Flags;
ULONG EntryPointToken;
// 绑定信息。
IMAGE_DATA_DIRECTORY Resources;
IMAGE_DATA_DIRECTORY StrongNameSignature;
// 一般的固定信息和绑定信息。
IMAGE_DATA_DIRECTORY CodeManagerTable;
IMAGE_DATA_DIRECTORY VTableFixups;
IMAGE_DATA_DIRECTORY ExportAddressTableJumps;
// 预编译的影像数据(只供内部使用,设为0)。
IMAGE_DATA_DIRECTORY ManagedNativeHeader;
} IMAGE_COR20_HEADER;
重申一点,作为.NET开发人员,我们并不需要关心Win32首部信息和CLR首部信息的具体细节(除非你需要编写一个新的托管编译器)。只需知道每一个.NET程序集都必须包含这些数据,它们会被.NET运行库和Win32操作系统所使用。
11.2.3 CIL代码、类型元数据和程序集清单
程序集的核心部分包含CIL代码,这些CIL代码是独立于平台和CPU的中间语言。在运行时,程序集内部的CIL代码才被(实时的JIT编译器)编译成特定平台和CPU的指令。在这种机制下,.NET程序集可以在多种不同的架构、设备和操作系统下运行。虽然可以完全不去了解CIL编程语言的细节,但是在第15章还是对其语法和语义进行了介绍。
程序集还包含元数据,这些元数据完整地描述了程序集内含类型和引用外部类型的格式。.NET运行库利用元数据在内存的二进制布局类型中解析类型(以及类型的成员)的位置,使远程方法调用更便利。在第12章探讨反射服务的时候,将会详细讲述.NET元数据的格式。
另外,程序集必须被关联上一个清单(manifest,也称程序集元数据)。该清单详细记录了程序集中的每一个模块、构建程序集的版本以及该程序集引用的所有外部程序集(在以往的COM类型库中并不记录这些外部依赖关系)。在本章中,我们将会看到CLR广泛地使用程序集的清单来定位外部程序集引用。
注解 如果想要查看一个程序集的CIL代码、类型元数据或者清单,ildasm.exe工具将会是不二之选。在完成本章所有代码示例的过程中,我们离不开它。
11.2.4 可选的程序集资源
最后,.NET程序集还可以包含一些嵌入资源,如应用程序图标、图像文件、声音片段或者字符串表。事实上,.NET平台支持附属程序集(satellite assemblies),这些程序集只包含本地化资源。在构建国际化软件系统的时候,我们可能想基于特定区域(英语、德语等)来对资源进行分类打包,这时候附属程序集就显得非常有用。附属程序集不是本书要讨论的话题,但在第20章GDI+部分,我们将介绍如何把应用程序资源嵌入到程序集里面。
11.2.5 单文件程序集和多文件程序集
从技术角度上说,程序集可以由多个模块组成。实际上,模块这个术语一般用于表示一个合法的.NET二进制文件。在大多数情况下,程序集只由一个模块组成。这种情况下,(逻辑)程序集和实际的(物理)二进制文件是一一对应的(因此被称作单文件程序集)。
单文件程序集的所有必要的部分(首部信息、CIL代码、类型元数据、清单和必需的资源)都包含在一个*.exe或者*.dll包中。图11-3展示了一个单文件程序集的组成。
另一方面,多文件程序集是一个.NET *.dll的集合,这些DLL作为单个逻辑单元进行部署和版本化。通常,其中的一个会作为主模块,它将包含程序集级别的清单(以及必要的CIL代码、元数据、头信息和可选资源)。主模块的清单记录了它依赖的每一个*.dll文件。
根据命名习惯,多文件程序集的辅助模块的文件扩展名一般是*.netmodule,但这不并是CLR的强制要求。辅助模块*.netmodules也包含CIL代码和类型元数据,同时还有一个模块级别的清单,该清单只记录了该模块外部引用的程序集。
多文件程序集的主要优点是:提高了程序下载的效率。例如,假设有一个客户端引用了远程的一个多文件程序集(包含3个模块),其中的主模块已经安装在客户端。如果客户端需要使用远程*.netmodule中的类型的话,那么CLR将会按需(仅当要使用时)把二进制模块下载到本地的下载缓存(download cache)中。如果每一个*.netmodule模块大小是1MB的话,那么好处显而易见。
多文件程序集的另外一个优点是:允许模块由不同的.NET编程语言进行编写(这一点对大型软件公司非常有用,因为每一个部门都可以使用自己喜欢的.NET语言)。一旦每一个独立的模块被编译好,我们可以使用诸如程序集连接器(al.exe)这样的工具把所有的模块连接成一个逻辑程序集。
无论如何,请记住:组成一个多文件程序集的模块并没有相互连接成一个大文件,相反,它们只是依靠在主模块清单中记录的信息逻辑地连接在一起。图11-4展示了一个包含3个模块的多文件程序集,其中每一个模块都使用不同的.NET编程语言来编写。
|
|
|
||||||||||||||||||||||||||||||||||
|
图11-3 单文件程序集 |
图11-4 主模块在程序集清单中记录了所需的辅助模块 |
到这里,希望读者已经对.NET二进制文件的内部组成有了一个更好的理解。在这些必要的前言介绍之后,我们将进入实际的构建和配置代码库的环节。






