我们在使用C# 语言的Assembly.Load 来加载托管程序集并使用反射功能时,一般需要先通过Assembly.Load(), Assembly.LoadFrom() 等方法将目标托管程序集加载到当前应用程序域中,然后生成对应实例,最后再进行调用实例的属性或者方法。
一般情况下,我们调用Assembly.Load 一类方法是不会出问题的,但是对于以下几种情况Assembly.Load 方法无法处理:
- 程序集可能是延迟签名的。
- 程序集可能被CAS 策略保护。
- 宿主程序与目标程序集的处理器架构不同。
- 当加载目标程序集时,目标程序集中的方法可能正在运行。 (比如,模块初始化)
- 程序集可能应用了绑定策略, 你可能不会得到你想要的那个程序集。
我们现在关注第四种情况,因为这种情况是最常见的。我们思考以下几个问题:
1. 为什么目标程序集的方法在运行时不允许再加载一次?
准确地说是为什么在一个应用程序域(AppDomain)中加载后的程序集默认不允许再另外一个应用程序域中加载?这是因为在第一次加载应用程序集时,Assemlby.Load 方法会将此程序集锁住,以防止在自己使用过程中应用程序集被其他应用程序修改(一般指删除)。这其实与Win32 API 中的CreateFile 函数行为类似,我们都知道,在 Windows 中去占用一个文件最直接、最简单的方式就是调用 CreateFile API 函数来打开文件。具体请参照:
2. Assembly.Load 方法能否实现在加载目标程序集时不锁定它?
我们可以使用如下代码加载我们的程序集:
byte[] buffer = System.IO.File.ReadAllBytes(yourFullfileNamePath); //Load assembly using byte array Assembly assembly = Assembly.Load(buffer);
后台的实现是暂时先将目标程序集锁定,然后把程序集内容复制到内存中,读取后将程序集解锁并从内存中加载目标程序集的拷贝。
如果你需要通过上面的方法加载GAC 中的程序集,那么可以通过如下代码实现:
string assemblyName = "AssemblyTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=fffb45e56dd478e3"; Assembly ass = Assembly.ReflectionOnlyLoad(assemblyName); byte[] buffer = System.IO.File.ReadAllBytes(ass.Location); Assembly assembly = Assembly.Load(buffer);
3. Assembly.Load 方法的缓存
在我们使用Assembly.Load 系列方法加载目标程序集时,可能有各种情况导致加载失败,最常见的是目标程序集不存在而导致加载失败问题。失败后我们可能想要再加载一次或者加载多次直到成功为止,但是在.NET Framework 2.0 以后默认是无法实现的,原因在于.NET Framework 2.0 以后 Assembly.Load 方法有缓存,第一次加载目标程序集的失败或者成功的状态都会被缓存,这样在你下一次加载目标程序集时不会真的加载,会直接从缓存里取目标程序集的内容和状态。
对这种情况我们可以在调用Assembly.Load 方法的宿主程序的app.config 中加入如下配置信息来禁用缓存:
<?xml version="1.0"?> <configuration> <runtime> <disableCach<b style="color:transparent">本文来源gao@!dai!ma.com搞$$代^@码!网!</b>ingBindingFailures enabled="1" /> </runtime> <startup> <supportedRuntime version="v2.0.50727"/> </startup> </configuration>
4. 还有其他方案吗?
CLR 框架略图: