
1.3 程序集
程序集是.NET Framework应用程序的构造块,它构成了部署、版本控制、重复使用、激活范围控制和安全权限的基本单元。本节将详细介绍程序集的相关内容,如程序集的内容、可执行功能以及程序集清单等。
1.3.1 程序集概述
程序集是为协同工作而生成的类型和资源的集合,这些类型和资源构成了一个逻辑功能单元。它向CLR提供了解类型实现所需要的信息,对于CLR来说,类型不存在于程序集上下文之外。
程序集是扩展名为.dll或.exe的文件,它是.NET Framework编程的基本组成部分,每个程序集只能有一个入口点(即DllMain、WinMain或Main)。使用程序集可以执行以下功能:
□ 包含CLR执行的代码。如果可移植可执行(PE)文件没有相关联的程序集清单,则将不执行该文件中的Microsoft中间语言(MSIL)代码。
□ 程序集形成安全边界。程序集就是在其中请求和授予权限的单元。
□ 程序集形成类型边界。每一类型的标识均包括该类型所驻留的程序集名称,在一个程序集范围内加载的MyType类型不同于在其他程序集范围内加载的MyType类型。
□ 程序集形成引用范围边界。程序集的清单包含用于解析类型和满足资源请求的程序集元数据,它指定在该程序集之外公开的类型和资源。
□ 程序集形成版本边界。程序集是公共语言运行时中最小的可版本化单元,同一程序集中的所有类型和资源均会被版本化为一个单元。
□ 程序集形成部署单元。当一个应用程序启动时,只有该应用程序最初调用的程序集必须存在。
□ 程序集是支持并行执行的单元。
程序集可以是静态的,也可以是动态的。静态程序集存储在磁盘上的可移植可执行(PE)文件中,它可以包括.NET Framework类型(接口和类)以及该程序集的资源(如位图、JPEG文件和资源文件等)。
动态程序集直接从内存运行并且在执行前不存储到磁盘上,但是在执行动态程序集后可以将它们保存到磁盘上,开发人员可以使用.NET Framework来创建动态程序集。
程序集创建时有多种方法,常用方法有3种。如下所示:
□ 使用用来创建.dll或.exe文件的开发工具,例如Visual Studio 2010。
□ 使用在.NET Framework SDK中提供的工具来创建带有在其他开发环境中创建的模块的程序集。
□ 使用CLR API(例如Reflection.Emit)来创建动态程序集。
1.3.2 程序集优点
程序集旨在简化应用程序部署并解决在基于组件的应用程序中可能出现的版本控制问题。目前,Win32应用程序存在两类版本控制问题:
□ 版本控制规则不能在应用程序的各段之间表达,并且不能由操作系统强制实施。目前的办法依赖于向后兼容,而这通常很难保证。
□ 没有办法在创建到一起的多套组件集与运行时提供的那套组件之间保持一致。
上述两类版本控制问题结合在一起产生了DLL冲突,在这些冲突中安装一个应用程序可能会无意间破坏现有的应用程序,因为所安装的某个软件组件或DLL与以前的版本不完全向后兼容。
为了解决版本控制问题以及导致的DLL冲突的其余问题,运行时使用程序集来执行以下功能:
□ 使开发人员能够指定不同软件组件之间的版本规则。
□ 提供强制实施版本控制规则的结构。
□ 提供允许同时运行多个版本的软件组件(称作并行执行)的基本结构。
通过在.NET Framework中使用程序集,可以使许多开发问题得到解决,因为程序集不依赖于注册表项的自述组件,所以程序集使无相互影响的应用程序安装成为可能,程序集还使应用程序的卸载和复制得以简化。
1.3.3 程序集内容
一般情况下,静态程序集由4个元素组成:程序集清单、类型元数据、实现这些类型的Microsoft中间语言(MSIL)代码和资源集。在这4种元素中,程序集清单是必需的,但是它也需要类型或资源来向程序集提供任何有意义的功能。
程序中的元素分组有几种方法,开发人员可以将所有元素分组到单个物理文件中,或者可以将一个程序集的元素包含在几个文件中,这些文件可能是编译代码的模块或应用程序所需的其他文件,如图1-3和图1-4分别显示了单文件程序集结构图和多文件程序集结构图。

图1-3 单文件程序集的结构图

图1-4 多文件程序集的结构图
在图1-4中的3个文件属于一个程序集,对于文件系统而言,它们是独立的文件,但是Until.net被编译为一个模块,它不包含任何程序集信息。当创建了程序集后,该程序集清单被添加到MyAssembly.dll,指示程序集与Until.net模块和Graphic.hmp的关系。
1.3.4 程序集清单
每一个程序集,无论是静态的还是动态的,都包含描述该程序集中各元素彼此如何关联的数据集合,程序集清单就包含这些程序集元数据。程序集清单包含指定该程序集的版本要求和安全标识所需的所有元数据,以及定义该程序集的范围和解析对资源和类的引用所需的全部元数据。
程序集清单可以存储在具有Microsoft中间语言(MSIL)代码的PE文件(.exe或.dll)中,也可以存储在只包含程序集清单信息的独立PE文件中。图1-5和图1-6根据程序集的类型分别显示了清单的不同存储方法。

图1-5 单文件程序集的存储方法

图1-6 多文件程序集的存储方法
从图1-5中可以看出,对于一个关联文件的程序集,该清单将被合并到PE文件中以构成单文件程序集。相关人员可以创建独立的清单文件,或清单被合并到同一多文件程序集中某一PE文件的多文件程序集。
每一个程序集的清单都执行以下功能:
□ 枚举构成该程序集文件。
□ 控制对该程序集的类型和资源的引用如何映射到包含其声明和实现的文件。
□ 枚举该程序集所依赖的其他程序集。
□ 在程序集的使用者和程序集的实现详细信息的使用者之间提供一定程序的间接性。
□ 呈现程序集自述。
程序集清单包含了多项内容,如表1-1列出了清单中所包含的信息,其中前4项(程序集名称、版本号、区域性和强名称信息)内容构成了程序集的标识。
表1-1 程序集清单信息

1.3.5 全局程序集缓存
全局程序集缓存中存储了专门指定给由计算机中若干应用程序共享的程序集,安装有CLR的每台计算机都具有称为全局程序集缓存的计算机范围内的代码缓存。
应当仅在需要时才将程序集安装到全局程序集缓存中以进行共享。一般原则是:程序集依赖项保持专用,并在应用程序目录中定位程序集,除非明确要求共享程序集。另外,不必为了使COM互操作或非托管代码可以访问程序集而将程序集安装到全局程序集缓存。
将程序集部署到全局程序集缓存中的方法有两种:
□ 使用专用于全局程序集缓存的安装程序,这种方法是将程序集安装到全局程序集缓存的首选方法。
□ 使用Windows软件开发包(SKD)提供的名为全局程序集缓存工具的开发工具。
提示
在部署方案中,应该使用Windows Installer 2.0将程序集安装到全局程序缓存中,相关人员一般只在开发方案中使用全局程序集缓存工具,这是因为它不提供使用Windows Installer时可以提供的程序集引用计数功能和其他功能。
管理员通常使用访问控制列表(ACL)来保护systemroot目录,以控制写入和执行访问。因为全局程序集缓存安装在systemroot目录的子目录中,它继承了该目录的ACL,建议只允许具有管理员权限的用户从全局程序集缓存中删除文件。
在全局程序集缓存中部署的程序集必须具有强名称,将一个程序集添加到全局程序集缓存时必须对构成该程序集的所有文件执行完整性检查,缓存执行这些完整性检查以确保程序集未被篡改。
强名称是由程序集的标识加上公钥和数字签名组成的,通过签发具有强名称的程序集可以确保名称的全局唯一性。强名称还需要特别满足以下要求:
□ 强名称依赖于唯一的密钥对来确保名称的唯一性。
□ 强名称保护程序集的版本沿袭。
□ 强名称提供可靠的完整性检查。
1.3.6 程序集安全注意事项
生成程序集时可以指定该程序集运行所需的一组权限,是否将特定的权限授予程序集是基于证据的。使用证据有两种不同的方式,第一种是将输入证据与加载程序所收集的证据合并,以创建用于策略决策的最终证据集,这种方式的方法包括Assembly.load、Assembly.LoadFrom和Activator.CreateInstance。第二种方式是原封不动地使用输入证据作为用于策略决策的最终证据集,使用这种语义的方法包括Assembly.Load(byte[])和AppDomain.DefineDynamicAssembly()。
通过在将运行程序集的计算机上设置安全策略,相关人员可以授予一些可选的权限。如果希望代码可以处理所有潜在的安全异常,可以执行以下两种操作:
□ 为代码必须具有的所有权限插入权限请求,并预先处理在未授予权限时发生的加载时错误。
□ 不要使用权限请求来获取代码可能需要的权限,但一定要准备处理在未授予权限时发生的安全异常。
可以使用两种不同但是相互补充的方式对程序集进行签名,使用强名称或使用.NET Framework 1.0和1.1版本中的Signcode.exe或.NET Framework更高版本中的SignTool.exe。
使用强名称对程序集进行签名将向包含程序集清单的文件添加公钥加密。强名称签名帮助验证名称的唯一性,避免名称欺骗,并且在解析引用时向调用方法提供某标识。但是,任何新人级别都不会与一个强名称关联,这样Signcode.exe和SignTool.exe就变得十分重要。这两个签名工具要求发行者向第三方证书颁发机构证实其标识并获取证书,然后此证书将嵌入到文件中,并且管理员能够使用该证书来决定是否相信这些代码的真实性。
相关人员可以将强名称和使用Signcode.exe或SignTool.exe创建的数字签名一起提供给程序集,或者可以单独使用其中之一,这两个签名工具一次只能对一个文件进行签名,对于多文件程序集,可以对包含程序集清单的文件进行签名。强名称存储在包含程序集清单的文件中,但使用Signcode.exe或SignTool.exe创建的签名存储在该程序集清单所在的可迁移可执行(PE)文件中保留的槽中。
强名称和使用Signcode.exe或SignTool.exe进行签名确保了完整性,它们和其他相关技术共同作用可以确保程序集没有做过任何方式的改动,因此相关人员可以将代码访问安全策略建立在这两种形式的程序集证据基础上。
1.3.7 程序集版本控制
本节所介绍的程序集版本控制,仅对具有强名称的程序集进行版本控制。每一个程序集都使用两种截然不同的方法来表示版本信息。
1. 程序集的版本号
程序集的版本号与程序集名称及区域性信息都是程序集标识的组成部分。每一个程序集都有一个版本号作为其标识的一部分,因此,如果两个程序集具有不同的版本号,运行时就会将它们视作不同的程序集。此版本号实际表示为具有以下格式的4部分号码:
<主版本>.<次版本>.<生成号>.<修订号>
例如,版本1.5.1254.0中1表示主版本,5表示次版本,1254表示生成号,而0表示修订号。
2. 信息性版本
信息性版本是一个字符串,表示仅为提醒的目的而包括的附加版本信息。
使用CLR的程序集的所有版本控制都在程序集级别上进行,一个程序集的特定版本和依赖程序集的版本在该程序集的清单中记录下来,除非被配置文件中的显式版本策略重写,否则运行时的默认版本策略是应用程序只与它们生成和测试时所用的程序集版本一起运行。CLR主要通过4步解析程序集的绑定请求:
(1)检查原程序集引用,以确定该程序集的版本是否被绑定。
(2)检查所有适用的配置文件(应用程序配置文件、发行者策略文件和计算机的管理员配置文件)以应用版本策略。
(3)通过原程序集引用和配置文件中指定的任何重定向来确定正确的程序集,并且确定应绑定到调用程序集的版本。
(4)检查全局程序集缓存和在配置文件中指定的基本代码,然后使用在运行时如何定位程序集中解释的探测规则检查该应用程序的目录和子目录。