l 开始绑定
当运行时尝试解析对另一个程序集的引用时,就开始进行定位并绑定到程序集的进程。该引用可以是静态的,也可以是动态的。无论引用是对静态程序集的引用还是对动态程序集的引用,运行时均使用相同的解析过程。
a) 静态引用:在生成时,编译器在程序集清单的元数据中记录静态引用。
b) 动态引用:由于调用各种方法而动态构造的,加载方法详细介绍请参见 《(7)动态程序集加载Load()》 。
l 运行时使用以下步骤来【解析】程序集引用:
第 1 步:检查配置文件,确定版本
可以基于三个 XML 文件在不同的级别下配置程序集绑定行为 :
a) 应用程序配置文件,可用于启用安全模式。
b) 发行者策略文件,决定升级绑定的新版本。
c) 计算机配置文件,决定绑定的最终版本。
这些文件使用相同的语法,并且给特定程序集提供诸如绑定重定向、代码位置以及绑定模式等信息。
EG:(详细语法请参见 《(4)绑定程序集配置策略》)
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="asm6" publicKeyToken="c0……" />
<bindingRedirect oldVersion="3.0.0.0" newVersion="2.0.0.0"/>
</dependentAssembly>
<dependentAssembly> ……</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
1. 应用程序配置文件
第一,在应用程序配置文件中,公共语言运行时检查覆盖调用程序集清单中存储的版本信息的信息。应用程序配置文件可以随应用程序一起部署,但它并不是应用程序执行所必需的。
a) 对于客户端可执行文件,应用程序配置文件与应用程序可执行文件位于同一个目录中,并且两者具有相同的基文件名,但应用程序配置文件的扩展名为 .config。
b) 在基于浏览器的方案中,HTML 文件必须使用 <link> 元素显式指向该配置文件。
2. 发行者策略文件(必须安装在全局程序集缓存)
第二,运行时检查发行者策略文件(如果存在的话)。发行者策略文件是由组件发行者作为共享组件的修正或更新分发的,即针对的是放入全局程序集缓存(GAC)中的程序集。这些文件包含共享组件发行者发布的兼容性信息,这些信息将程序集引用指向新版本,影响使用某个共享组件的所有应用程序。
发行者策略配置文件覆盖来自应用程序的版本信息(即,来自于程序集清单或应用程序配置文件)。
与应用程序配置文件和计算机配置文件不同,发行者策略文件包含在其自己的程序集中,该程序集必须安装在全局程序集缓存中。(al.exe + gacutil.exe)
----------安全模式
安全模式由 <publisherPolicy apply="yes |no"/> 元素决定,该元素仅位于应用程序配置文件中。安全模式指定是否应该从绑定过程中移除发行者策略配置信息。
通常,发行者策略文件是作为 Service Pack 或程序更新的一部分显式安装的。如果升级的共享组件有任何问题,则可以使用安全模式忽略发行者策略文件中的覆盖。(默认为yes)
a) 整个应用程序设置安全模式:设置 <publisherPolicy apply=no/>,并且置于 <assemblyBinding> 元素内不包含依赖程序集元素。
b) 特定的程序集设置安全模式:设置 <publisherPolicy apply=no/> 并放置在 <dependentAssembly> 元素中,使用 <dependentAssembly> 元素指定要影响哪些程序集。
3. 第三,计算机配置文件(管理员策略文件)
运行时检查计算机配置文件Machine.config位于本地计算机上安装【运行时】的根目录的 Config 子目录中。计算机配置文件中的设置优先于所有其他配置设置,确定的版本是最终版本,并且不能被覆盖。 Machine.config 文件中指定的覆盖影响所有的应用程序。
第 2 步:检查已经加载的程序集
在.NET Framework 2.0 以后比如 Assembly.Load 方法有缓存,第一次加载目标程序集的失败或者成功的状态都会被缓存,这样在你下一次加载目标程序集时会直接从缓存里取目标程序集的内容和状态。
当失败后我们可能想要再加载一次或者加载多次直到成功为止,这时需要在app.config 中加入如下配置信息来禁用缓存:
<runtime> <disableCachingBindingFailures enabled="1" /> </runtime>
注意: ReflectionOnlyLoad()\ReflectionOnlyLoadFrom() 只反射加载不会使用缓存。
第 3 步:检查全局程序集缓存
如果被引用的程序集是强命名的(换句话说,AssemblyRef包括了非空的公钥或公钥标记),那么接下来就会在GAC中查找这个程序集。否则,由于弱名称程序集不能安装在GAC中,就会跳过这一步。
第 4 步:通过基本代码或探测定位程序集
在使用调用程序集的引用中的信息和配置文件中的信息确定了正确的程序集【版本】之后,并且在公共语言运行时在全局程序集缓存中进行检查(仅检查具有强名称的程序集)之后,公共语言运行时就会尝试【查找】该程序集。定位程序集的过程包含以下步骤:
1) 如果在配置文件中找到对应的 <codeBase> 元素,则运行时会检查指定的位置。
配置文件的codeBase元素实际标记了一个URL。这个URL可引用用户机器上的任何目录,也可以引用一个Web地址。如果引用的是Web地址,CLR会自动下载文件,并把它存储到用户的下载缓存中(%UserProfile%\Local Settings\Application Data\Assembly下的一个子目录)。将来进行引用时,CLR会将已下载的文件的时间戳与指定URL处的文件的时间戳进行比对。如果URL处的文件具有较新的时间戳,CLR会下载新版本文件并加载它。否则,CLR会加载缓存的文件,不再重复下载(从而增强性能)。
如果找到匹配的程序集,则会使用该程序集,并且不会进行探测。如果CodeBase指定的可执行体与这个引用不匹配,会认为搜索是失败的,不会进行程序集加载。
2) 没有<codeBase>元素则运行时使用指定的规则探测引用的程序集。
l 通过探测定位程序集
如果应用程序配置文件中没有 <codeBase> 元素,则运行时使用以下四个条件来探测程序集:
1. 应用程序基,它是指向应用程序位置的URL(即指向应用程序所在的目录)。
a) 对于可执行体而言,它是包括EXE文件的目录。
b) 对于Web应用程序而言,AppBase是由Web服务器定义的应用程序根目录。
2. 区域性,它是被引用的程序集的区域性特性。
3. 名称,它是被引用的程序集的名称。
4. <probing> 元素的 privatePath 特性,这是根位置下用户定义的子目录列表(必须是应用程序根目录的子目录)。
---------探测应用程序基和区域性目录
运行时始终在应用程序基中开始探测,应用程序基可以是一个 URL,也可以是计算机上的应用程序根目录。如果在应用程序基中没有找到引用的程序集,并且未提供区域性信息,则运行时使用程序集名称搜索任何子目录。探测的目录包括:
a) [应用程序基] / [程序集名称].dll
b) [应用程序基] / [程序集名称] / [程序集名称].dll
如果指定了引用的程序集的区域性信息,则只探测以下目录:
a) [应用程序基] / [区域性] / [程序集名称].dll
b) [应用程序基] / [区域性] / [程序集名称] / [程序集名称].dll
---------使用 privatePath 特性进行探测
然后,运行时还探测使用 <probing> 元素的 privatePath 特性指定的目录。探测的目录包括:
a) [应用程序基] / [bin 路径] / [程序集名称].dll
b) [应用程序基] / [bin 路径] / [程序集名称] / [程序集名称].dll
如果包含区域性,则探测以下目录:
a) [应用程序基] / [bin 路径] / [区域性] / [程序集名称].dll
b) [应用程序基] / [bin 路径] / [区域性] / [程序集名称] / [程序集名称].dll
<publisherPolicy>与<codeBase> 应用区别请参见: 《(4)绑定程序集配置策略》
《反射机制》系列:
参考资源: