当程序集解析器确定将要加载的程序集版本之后,它必须定位一个适合的文件,并将其传递给底层的程序集加载器。CLR首先查找操作系统环境变量DEVPATH指定的目录。这个环境变量通常不在部署机器上设置。准确地说,它是由开发人员使用的,主要用于将延迟签署的程序集从共享文件系统目录下加载。此外,如果只有machine.config文件中存在下面的XML配置文件元素时,DEVPATH环境变量才被考虑:
<configuration>
<runtime>
<developmentMode developerInstallation="true" />
</runtime>
</configuration>
因为在部署时并不需要DEVPATH环境变量,所以,本章的剩余部分将忽略它的存在。
图2.12:程序的解析过程
图2.12展示了程序集解析器寻找适合的程序集文件的整个过程。在正常的部署场景中,程序集解析器首先从全局程序集缓存[global assembly cache(GAC)]中定位程序集。GAC是一个计算机范围的代码缓存,它所包含的已安装程序集适用于整个计算机。GAC允许管理人员按每计算机安装程序集,这样所有的应用程序都能够使用它。为了避免系统崩溃,GAC只接受具备有效数字签名和公钥的程序集。此外,只有管理人员能够删除GAC中的记录,这样就防止非管理人员删除或移动关键的系统级的组件。
为了避免二义性,程序集解析器只有在被请求的程序集名字中包含公钥时才查找GAC。这样,对于诸如utilities常见名字的请求,就能防止错误的实现被满足。公钥既能够作为程序集引用或者Assembly.Load参数的一部分显式地被提供,也可以通过qualityAssembly配置文件元素隐式地被提供。
GAC由系统级的组件(FUSION.DLL)控制,将DLL缓存存储在%WINNT%\Assembly目录下。FUSION.DLL将管理这个目录层次,并提供基于四部程序集名字的文件访问,如表2.4所示。尽管你能够遍历底层目录,但是,FUSION保存缓存DLL的模式是一个实现的细节,它将保证GAC随着CLR的介入而变化。因此,你必须通过GACUTIL.EXE工具或者FUSION的应用程序接口[Application
programming interface(API)]与GAC打交道。SHFUSION.DLL就是这样的一个外观(facade),它是一个Windows Explorer的外壳扩展(shell
extension),提供了到GAC的用户友好接口。
表2.4 全局程序集缓存
名字
|
版本
|
文化
|
公钥标记
|
节选路径
|
yourcode
|
1.0.1.3
|
de
|
89abcde...
|
t3s\e4\yourcode.dll
|
yourcode
|
1.0.1.3
|
en
|
89abcde...
|
a1x\bb\yourcode.dll
|
yourcode
|
1.0.1.8
|
en
|
89abcde...
|
vv\a0\yourcode.dll
|
libzero
|
1.1.0.0
|
en
|
89abcde...
|
ig\u\libzero.dll
|
如果程序集解析器不能在GAC中找到被请求的程序集,那么,程序集解析器将试图通过CODEBASE提示(CODEBASE hint)访问程序集。CODEBASE提示只是简单地将程序集名字映射为文件名字或URL,以定位包含程序集清单的模块。像版本策略一样,CODEBASE提示存在于应用程序范围和计算机范围的配置文件中。示例2.6是一个配置文件的示例,它包含两个CODEBASE提示:第一个提示将Acme.HealthCare程序集的1.2.3.4版映射到文件C:\acmestuff\Acme.HealthCare.DLL;第二个提示将相同程序集的1.3.0.0版映射到文件http://www.lmwlove.com/Acme.HealthCare.DLL。
假定已经提供了CODEBASE提示,程序集解析器能够简单地加载相应的程序集文件,并且加载过程如同通过显式的CODEBASE按Assembly.LoadForm方式加载一样。然而,如果提供没有CODEBASE提示,那么,程序集解析器就要启动一个潜在开销较大的流程,用于查找匹配请求的程序集文件。
示例2.6:使用配置文件指定CODEBASE
<?xml version="1.0" ?><configuration
xmlns:asm="urn:schemas-microsoft-com:asm.v1"
>
<runtime>
<asm:assemblyBinding>
<!-- one dependentAssembly per unique assembly name -->
<asm:dependentAssembly>
<asm:assemblyIdentity
name="Acme.HealthCare"
publicKeyToken="38218fe715288aac" />
<!-- one codeBase per version -->
<asm:codeBase
version="1.2.3.4"
href="file://C:/acmestuff/Acme.HealthCare.DLL"/>
<asm:codeBase
version="1.3.0.0" href="http://www.lmwlove.com/Acme.HealthCare.DLL"/>
</asm:dependentAssembly>
</asm:assemblyBinding>
</runtime>
</configuration>
如果程序集解析器通过GAC或CODEBASE提示都无法定位程序集,那么,程序集解析器将对与应用程序的根目录相关的目录进行查找。这个查找被称为探测(probing)。探测只在APPBASE目录及其子目录下进行(APPBASE目录是含有应用程序配置文件的目录)。例如,如图2.13所示的目录层次,只有目录m、common、shared以及q是适于探测。这说明程序集解析器将只探测显式地列在应用程序配置文件中的子目录。示例2.7是一个配置文件的示例,它将相关搜索路径设置为shared和common。配置文件中没有列出的APPBASE的所有子目录,将不再被搜索。
图2.13:APPBASE以及相关的查找路径
示例2.7:设置相对查找路径
<?xml version="1.0" ?>
<configuration
xmlns:asm="urn:schemas-microsoft-com:asm.v1"
>
<runtime>
<asm:assemblyBinding>
<asm:probing privatePath="shared;common" />
</asm:assemblyBinding>
</runtime>
</configuration>
当对程序集进行探测时,程序集解析器将根据程序集的简单名字、相关的查找路径以及程序集所要求的文化(如果在程序集引用中出现)构造CODEBASE URL。图2.14是一个CODEBASE的URL的示例,它将解析未指定文化的程序集引用。在这个例子中,程序集的简单名字是yourcode,相关的查找路径是shared目录和common目录。程序集解析器首先在APPBASE目录中查找名为yourcode.dll文件;如果没有这样的文件,解析器则假定程序集位于与程序集名字相同的目录(即yourcode目录)下,并查找带有该名字的文件;如果还是找不到,就会对每一个相关的查找路径重复执行该过程,直到找到名为yourcode.dll的文件。如果找到了该文件,探测就停止;否则,探测过程将被重复,不过,这次查找的文件是yourcode.exe。假定文件被找到了,程序集解析器将检验文件和在程序集引用中指定的程序集名字的所有属性是否匹配,然后加载这个程序集。如果该文件的程序集名字中有一个属性不能匹配所有(后版本策略)程序集引用的属性,Assembly.Load调用就会失败。否则,程序集被加载,并准备投入使用。
当程序集引用包含文化标识符时,探测就相对复杂一些。如图2.15所示,前面的算法将增加一些对子目录的查找,目录的名字与所要求的文化匹配。一般情况下,应用程序应该保持较少的相对查找路径,这样可以避免过长的加载时间。
图2.4:文化无关的探测
NL垊v皨U_{|媁<