第三章 共享程序集
Ø 3.1 两种程序集,两种部署方式
.NET框架支持两种程序集:弱命名程序集(weakly named assembly)和强命名程序集(strongly named assembly),二者之间的真正区别在于,强命名程序集有一个发布者的公钥/私钥对签名,其中的公钥/私钥对唯一地标识了程序集的发布者。利用公钥/私钥对,我们可以对程序集进行唯一的标识,实施安全策略和版本策略,从而可以将其部署在用户硬盘的任何地方,甚至互联网上。这种唯一标识程序集的能力使得应用程序在试图绑定一个强命名程序集时,CLR能够实施某些“已确知安全”的策略。
一个程序集有两种部署方式:即私有方式和全局方式。私有部署方式将程序集部署在应用程序的基目录及其子目录下。弱命名程序集只能够进行私有部署。
Ø 3.2 强命名程序集
强命名程序集包含四个唯一标识程序集的特性:文件名(没有扩展名)、版本号、语言文化标识和一个公有密钥标记。
SN.exe的所有命令行开关都是区分大小写的。
公有密钥标记是一个64位的公有密钥散列值。SN.exe的-tp命令行开关在输出的末尾显示了和完整的公有密钥相对应的公有密钥标记。
编译时,编译器会根据[assembly:AssemblyKeyFile(“MyCompany.keys”)]中指定的文件对程序集进行签名,并将共有密钥嵌入到清单中。只能对包含清单的那个程序集文件进行签名。
当生成一个强命名程序集时,该程序集的FileDef清单元数据表将包含组成该程序集的所有文件的一个列表。当每个文件的名称被加入到清单中时,该文件的内容也被转换成一个散列值,该散列值将和文件名一起存入FileDef表中。
发布者的公有密钥也被嵌入到PE文件的AssemblyDef清单元数据中
编译时必须为编译器指定所引用的程序集,对于C#编译器来说,可以使用/reference命令行开关来实现这一点。编译器的部分工作就是在生成的托管模块中嵌入一个AssemblyRef元数据表。AssemblyRef元数据表中的每一个条目都标识着一个被引用的程序集的名称(不含路径和扩展名),版本号,语言文化及公有密钥信息。
我们可以指定AL.exe的/keyfile命令行开关来代替使用AssemblyKeyFileAttribute特性。
AssemblyDef条目总是存储着整个共有密钥,而不是共有密钥标记
Ø 3.3 全局程序集缓存
GAC(Global Assembly Cashe) 全局程序集缓存,通常位于C:windows/assembly/GAC
不能将一个弱命名的程序集安装到GAC中
Ø 3.4 引用强命名程序集
如果/reference指定的是一个不带路径的文件名,csc.exe将在以下目录中查找程序集:
1. 当前工作目录
2. 编译器当前使用的CLR所在的目录
3. 任何用csc.exe的/lib命令行开关指定的目录
4. 任何LIB环境变量中指定的目录
如果引用System.Drawing.dll,编译时发现程序集的地方是CLR所在的目录,但运行时加载程序集的地方是GAC。安装.NET框架时,会有两份微软的程序集文件拷贝并安装。其中一份是被装在CLR所在的目录中,另一份被装在GAC目录中。CLR所在目录的拷贝使得我们可以方便地生成自己的程序集。而GAC中的拷贝用于运行时加载这些程序集文件。
响应文件是一个包含一组编译器命令行开关的文本文件。当执行CSC.exe命令时,编译器会打开响应文件,并且就像使用通过命令行传递的开关一样使用响应文件中指定的命令行开关。响应文件非常方便,因为我们不必再每次编译项目时都用手动来表达期望的命令行参数。
如果本地响应文件和全局响应文件中的设置有冲突,本地响应文件中的设置将覆盖全局响应文件中的设置。
当我们安装.NET框架时,它会同时安装一个默认的全局CSC.rsp文件。
Ø 3.5强命名程序集的防篡改特性
向GAC添加程序集时的防篡改验证:
用私有密钥为程序集签名可以确保该程序集的生产者未对应公有密钥的持有者。当程序集被安装到GAC目录内时,系统将会包含清单的文件内容进行散列转换,并用得到的散列值来和嵌入在PE文件中的RSA数字签名进行比较。如果两个值相同,则证明程序集文件的内容没有被篡改,并且还可以知道我们拥有着和发布者的私有密钥相对应的共有密钥。另外程序还会对程序集中其他文件的内容进行散列转换,然后将得到的散列值和清单文件中的FileDef表内存储的散列值进行比较。如果发现有任何不匹配的情况,则证明至少有一个程序集的文件被篡改了,程序集向GAC中的安装将告失败。
Ø 3.6 延迟签名
使用延迟签名开发程序集过程:
1. 当开发程序集时,首先取得仅包含公司共有密钥的文件
2. 生成程序集后,通过下列命令将程序集安装到GAC中,或者生成引用程序集的其他程序集,以及测试该程序集
SN.exe –Vr MyAssembly.dll
3. 当准备打包和部署程序集时,取得公司的私有密钥(即为公钥/私钥对),执行下面的命令:
SN.exe –R MyAssembly.dll MyCompanyPrivateKey.keys
4. 执行下面的命令,恢复验证过程以进行测试:
SN –Vu MyAssembly.dll
Ø 3.7 强命名程序集的私有部署
将程序集部署到GAC的好处:
1. GAC使得很多应用程序可以共享程序集,这从整体上减少了使用的物理内存
2. 我们很容易将一个新版的程序集部署到GAC中,并允许所有的应用程序通过一种发布者策略来使用这个新的版本。
3. GAC还提供了对不同版本程序集的并存管理方式
将程序集部署到GAC的坏处:
1. GAC的安全策略只允许管理员来安装程序集
2. 向GAC安装程序集也破坏了.NET框架简单拷贝部署的许诺
3. GAC中新版的程序集不会覆盖旧版的程序集,它们会以并存的方式安装,非常耗费磁盘空间
仅当程序集被多个应用程序共享时,才考虑将它们部署到GAC中
除了以GAC或者私有的方式部署强命名程序集之外,我们还可以将强命名程序集部署在仅为一小部分应用程序知道的某个任意的目录中。采用这样的安装方式,当我们将每个应用程序安装到各自的目录中时,我们还需要为它们安装一个XML配置文件,并将共享程序集的codeBase元素指向其所在的实际路径。这种技术很少使用,因为没有哪个程序集能够控制共享程序集文件何时应该被卸载。
Ø 3.8 并存执行
CLR能够将名称相同的路径但路径不同的多个文件加载在同一个地址空间,这在.NET框架中成为并存(side-by-side)执行,它是解决DLL hell问题的关键技术
Ø 3.9 CLR如何解释类型引用
在解释一个被引用类型时,CLR将从下述三个地方找到该类型:
同一个文件;不同的文件,相同的程序集;不同的文件,不同的程序集
CLR加载被引用类型的过程:
CLR首先搜索其实现程序集,并加载包含程序集清单的PE文件。CLR然后会扫描清单以确定该PE文件实现了所需的类型。如果清单文件中包含了被引用的类型,加载过程将告完成。但如果被引用的类型出现在程序集的另外一个文件中,CLR会加载该文件,扫描其中的元数据并定位需要的类型。完成加载后,CLR会创建一个内部的数据结构来表示该类型。JIT编译器接着将完成Main方法的编译并开始执行Main方法。
Ø 3.10 高级管理控制(配置)
编译时方法的定位:
当编译一个方法时,CLR会确定当中其中所引用的类型和成员。由此CLR进一步确定当初生成调用程序集时,都引用了那些程序集,这可以查找调用程序集的AssemblyRef表来获得,之后CLR会在应用程序的配置文件中查找程序集,同时会应用其中指定的版本号重定向策略。查找程序集时,CLR首先会到机器内的Machine.config文件中查找程序集,并应用任何其中指定的版本号重定向策略。最后CLR将知道它应该加载的是哪个版本的程序集,并且首先从GAC中加载它。