通过应用程序域AppDomain加载和卸载程序集
微软装配车的大门似乎只为货物装载敞开大门,却将卸载工人拒之门外。车门的钥匙只有一把,若要获得还需要你费一些心思。我在学习Remoting的时候,就遇到一个扰人的问题,就是Remoting为远程对象仅提供Register的方法,如果你要注销时,只有另辟蹊径。细心的开发员,会发现Visual Studio.Net中的反射机制,同样面临这个问题。你可以找遍MSDN的所有文档,在Assembly类中,你永远只能看到Load方法,却无法寻觅到Unload的踪迹。难道我们装载了程序集后,就不能再将它卸载下来吗?
想一想这样一个场景。你通过反射动态加载了一个dll文件,如今你需要在未关闭程序的情况下,删除或覆盖该文件,那么结果会怎样?很遗憾,系统会提示你无法访问该文件。事实上该文件正处于被调用的状态,此时要对该文件进行修改,就会出现争用的情况。
显然,为程序集提供卸载功能是很有必要的,但为什么微软在其产品中不提供该功能呢?CLR 产品单元经理(Unit Manager) Jason Zander 在文章 Why isn't there an Assembly.Unload method? 中解释了没有实现该功能的原因。Flier_Lu在其博客里(Assembly.Unload)有详细的中文介绍。文中介绍了解决卸载程序集的折中方法。Eric Gunnerson在文章《AppDomain 和动态加载》中也提到:Assembly.Load() 通常运行良好,但程序集无法独立卸载(只有 AppDomain 可以卸载)。Enrico Sabbadin 在文章《Unload Assemblies From an Application Domain》也有相关VB.Net实现该功能的相关说明。
尤其是Flier_Lu的博客里已经有了很详细的代码。不过,这些代码没有详细地说明。我在我的项目中也需要这一项功能。这段代码给了我很大的提示。但在实际的实现中,还是遇到一些具体的问题。所以我还是想再谈谈我的体会。
通过AppDomain来实现程序集的卸载,这个思路是非常清晰的。由于在程序设计中,非特殊的需要,我们都是运行在同一个应用程序域中。由于程序集的卸载存在上述的缺陷,我们必须要关闭应用程序域,方可卸载已经装载的程序集。然而主程序域是不能关闭的,因此唯一的办法就是在主程序域中建立一个子程序域,通过它来专门实现程序集的装载。一旦要卸载这些程序集,就只需要卸载该子程序域就可以了,它并不影响主程序域的执行。
不过现在看来,最主要的问题不是子程序域如何创建,关键是我们必须实现一种机制,来达到两个程序域之间完成通讯的功能。如果大家熟悉Remoting,就会想到这个问题不是和Remoting的机制有几分相似之处吗?那么答案就可以呼之欲出了,对了,就是使用代理的方法!不过与Remoting不同的是两个程序域之间的关系。因为子程序域是在主程序域中建立的,因此对该域的控制显然就与Remoting不相同了。我想先用一副图来表述实现的机制:

说明:
1、Loader类提供创建子程序域和卸载程序域的方法;
2、RemoteLoader类提供装载程序集方法;
3、Loader类获得RemoteLoader类的代理对象,并调用RemoteLoader类的方法;
4、RemoteLoader类的方法在子程序域中完成;
5、Loader类和RemoteLoader类均放在AssemblyLoader.dll程序集文件中;
我们再来看代码:
Loader类:
SetRemoteLoaderObject()方法:
private AppDomain domain = null;
private Hashtable domains = new Hashtable();
private RemoteLoader rl = null;
public RemoteLoader SetRemoteLoaderObject(string dllName)
{
AppDomainSetup setup = new AppDomainSetup();
setup.ShadowCopyFiles = "true";
domain = AppDomain.CreateDomain(dllName,null,setup); domains.Add(dllName,domain);
try
{
rl = (AssemblyLoader.RemoteLoader)domain.CreateInstanceFromAndUnwrap(
"AssemblyLoader.dll","AssemblyLoader.RemoteLoader");
}
catch
{
throw new Exception();
}
}
代码中的变量rl为RemoteLoader类对象,在Loader类中是其私有成员。SetRemoteLoaderObject()方法实际上提供了两个功能,一是创建了子程序域,第二则是获得了RemoteLoader类对象。
请大家一定要注意语句:
rl = (AssemblyLoader.RemoteLoader)domain.CreateInstanceFromAndUnwrap("AssemblyLoader.dll","AssemblyLoader.RemoteLoader");
这条语句就是实现两个程序域之间通讯的关键。因为Loader类是在主程序域中,RemoteLoader类则是在子程序域中。如果我们在Loader类即主程序域中显示实例化RemoteLoader类对象rl,此时调用rl的方法,实际上是在主程序域中调用的。因此,我们必须使用代理的方式,来获得rl对象,这就是CreateInstanceFromAndUnwrap方法的目的。其中参数一为要创建类对象的程序集文件名,参数二则是该类的类型名。
CreateCreateInstanceFromAndUnwrap方法有多个重载。代码中的调用方式是当RemoteLoader类为默认构造函数时的其中一种重载。如果RemoteLoader类的构造函数有参数,则方法应改为:
object[] parms = {dllName};
BindingFlags bindings = BindingFlags.CreateInstance |
BindingFlags.Instance | BindingFlags.Public;
rl = (AssemblyLoader.RemoteLoader)domain.CreateInstanceFromAndUnwrap("AssemblyLoader.dll","AssemblyLoader.RemoteLoader",true,bindings,
null,parms,null,null,null);
详细的调用方式可以参考MSDN。
以下Loader类的Unload方法和LoadAssembly方法():
public Assembly LoadAssembly(string dllName)
{
try
{
SetRemoteLoaderObject(dllName);
return rl.LoadAssembly(dllName);
}
catch (Exception)
{
throw new AssemblyLoadFailureException();
}
}
public void Unload(string dllName)
{
if (domains.ContainsKey(dllName))
{
AppDomain appDomain = (AppDomain)domains[dllName];
AppDomain.Unload(appDomain);
domains.Remove(dllName);
}
}
当我们调用Unload方法时,则程序域domain加载的程序集也将随着而被卸载。LoadAssembly方法中的异常AssemblyLoadFailureException为自定义异常:
public class AssemblyLoadFailureException:Exception
{
public AssemblyLoadFailureException():base()
{
} public override string Message
{
get
{
return "Assembly Load Failure";
}
} }
既然在Loader类获得的RemoteLoader类实例必须通过代理的方式,因此该类对象必须支持被序列化。所以我们可以令该类派生MarshalByRefObject。RemoteLoader类的代码:
public class RemoteLoader:MarshalByRefObject
{
public RemoteLoader(string dllName)
{
if (assembly == null)
{
assembly = Assembly.LoadFrom(dllName);
}
} private Assembly assembly = null; public Assembly LoadAssembly(string dllName)
{
try
{
assembly = Assembly.LoadFrom(dllName);
return assembly;
}
catch (Exception)
{
throw new AssemblyLoadFailureException();
}
}
}
通过上述的两个类,我们就可以实现程序集的加载和卸载。另外,为了保证应用程序域的对象在内存中被清除,应该令这两个类都实现IDisposable接口,和实现Dispose()方法。
然而在实际的操作过程中,我发现在RemoteLoader类的LoadAssembly方法,是存在遗患的。在我的LoadAssembly方法中,会返回一个Assembly对象。令我百思不得其解的是,虽然都是Assembly对象,但在加载某些程序集并返回Assembly时,在Loader类中会抛出SerializationException异常,并报告反序列化的对象状态不足。这个异常是在序列化获反序列化过程中发生的。我反复比较了两个程序集,一个可以正常加载并序列化,一个会抛出如上异常。会抛出异常的程序集并没有什么特殊之处,且我在程序中的其他地方也没有重复加载该程序集。这是一个疑问!!
不过通常我们在RemoteLoader类中,要实现的方法并非返回一个Assembly对象,而是通过反射加载程序集后,创建该程序集的对象。由于类对象都为object类型,此时序列化就不会出现问题。在我的项目中,因为要获得程序集的版本号,比较版本号在确定是否需要更新,因此我在RemoteLoader类中,只需要在加载程序集后,返回程序集的版本号字符串类型就可以了。字符串类型是绝对支持序列化的。
AssemlbyLoader.Dll的源代码可以点击这里获得。在应用程序中,显示添加对该程序集的引用,然后实例化Loader类对象,来调用该方法即可。我还做了一个简单的测试程序,用的是LoadAssembly方法。大家可以测试一下,是否如我所说,对于某些程序集,可能会抛出序列化的异常!?
测试的代码请点击这里获得,测试界面如下:

同时,大家也可以测试一下,直接加载和通过AppDomain加载,删除程序集文件时会有什么区别?
原文链接:
通过应用程序域AppDomain加载和卸载程序集
通过应用程序域AppDomain加载和卸载程序集的更多相关文章
- C#.Net 如何动态加载与卸载程序集(.dll或者.exe)2----通过应用程序域AppDomain加载和卸载程序集之后,如何再返回原来的主程序域
实现目的:动态加载dll,执行完毕之后可以随时卸载掉,并可以替换这些dll,以在运行中更新dll中的类. 其实就是通过应用程序域AppDomain加载和卸载程序集. 在这方面微软有篇文章http:// ...
- C#.Net 如何动态加载与卸载程序集(.dll或者.exe)0-------通过应用程序域AppDomain加载和卸载程序集
本博客中以“C#.Net 如何动态加载与卸载程序集(.dll或者.exe)”开头的都是引用莫问奴归处 微软装配车的大门似乎只为货物装载敞开大门,却将卸载工人拒之门外.车门的钥匙只有一把,若要获得还需要 ...
- C#.Net 如何动态加载与卸载程序集(.dll或者.exe)3---- 动态加载Assembly应用程序
下载 supergraphfiles.exe 示例文件. 应用程序体系结构 在我专攻代码之前,我想谈谈我尝试做的事.您可能记得,SuperGraph 让您从函数列表中进行选择.我希望能够在具体的目录中 ...
- C#.Net 如何动态加载与卸载程序集(.dll或者.exe)1----C#中动态加载和卸载DLL
我们知道在C++中加载和卸载DLL是一件很容易的事,LoadLibrary和FreeLibrary让你能够轻易的在程序中加载DLL,然后在任何地方卸载. 在C#中我们也能使用Assembly.Load ...
- C#.Net 如何动态加载与卸载程序集(.dll或者.exe)4-----Net下的AppDomain编程 [摘录]
最近在对AppDomain编程时遇到了一个问题,卸载AppDomain后,在内存中还保留它加载的DLL的数据,所以即使卸载掉AppDomain,还是无法更新它加载的DLL.看来只有关闭整个进程来更新D ...
- C#.Net 如何动态加载与卸载程序集(.dll或者.exe)6-----在不卸载程序域的前提下替换程序集文件。
当某个程序集文件被载入AppDomain,该文件在AppDomain.Unload之前是不能被替换和删除的.使用AppDomainSetup的影像复制功能可以实现在不卸载程序的情况下替换或者删除程序集 ...
- C#.Net 如何动态加载与卸载程序集(.dll或者.exe)5-----Assembly.Unload
http://www.blogcn.com/user8/flier_lu/index.html?id=2164751&run=.04005F8 CLR 产品单元经理(Unit Manager) ...
- c# 动态加载和卸载DLL程序集
原文:c# 动态加载和卸载DLL程序集 在 C++中加载和卸载DLL是一件很容易的事,LoadLibrary和FreeLibrary让你能够轻易的在程序中加载DLL,然后在任何地方卸载.在 C#中我们 ...
- C#中动态加载和卸载DLL
在C++中加载和卸载DLL是一件很容易的事,LoadLibrary和FreeLibrary让你能够轻易的在程序中加载DLL,然后在任何地方卸载.在C#中我们也能使用Assembly.LoadFile实 ...
随机推荐
- java设计模式-----16、解释器模式
概念: Interpreter模式也叫解释器模式,是行为模式之一,它是一种特殊的设计模式,它建立一个解释器,对于特定的计算机程序设计语言,用来解释预先定义的文法.简单地说,Interpreter模式是 ...
- MySql某一列累计查询
问题:有一列数据,需要累计显示出来 比如:id salary 查询结果:id salary sumSalary 1 10000 1 10000 ...
- Excel2010取消隐藏的工作簿
背景 Excel 2010文件,其中包含针对业务需要涉及的计算器等,其中一个Worksheet用于存放计算器用到的常量,针对业务人员(即此Excel文件的用户)是隐藏的,并有密码保护. 现象 因业务变 ...
- Vue 框架-11-介绍src文件流程及根组件app+HBuilder 配置
Vue 框架-11-介绍src文件流程及根组件app+HBuilder 配置 这是上一篇对目录简单介绍: 关于编辑器,可以使用轻量级的 Sublime Text 3,我使用的是 HBuilder, 但 ...
- mybatis 一对一 映射实体类、嵌套查询
一对一 在SysUser 类中增加SysRole字段.1.sql语句将role.role_name映射到role.roleName上. 2.还可以在XML 映射文件中配置结果映射.<result ...
- 项目maven update 后启动项目出现导常:org.springframework.web.context.ContextLoaderListener
导常:org.springframework.web.context.ContextLoaderListener 1. 右键单击工程项目 ->点击 properties2. 选择 Deploym ...
- C++ 随机数字以及随机数字加字母生成
#include <time.h>#include <sys/timeb.h>void MainWindow::slot_clicked(){ QString strRand; ...
- SQL Server ->> 使用CROSS APPLY语句是遇到聚合函数中包含外部引用列时报错
本次遇到的问题是CROSS APPLY的内部查询语句中的聚合函数包含CASE WHEN判断,且同时又内部语句的表的列和外部引用的表的列,此时会报下列的错误. 消息 8124,级别 16,状态 1,第 ...
- MySQL慢日志简介及Anemometer工具介绍
作者:王航威 - fordba.com 来源:http://fordba.com/box-anemometer-visual-mysql-slow.html,叶师傅对原文内容略有调整 备注:王航威是知 ...
- 使用GTID给Galera集群做数据库异步复制
一.为什么要做Galera集群异步复制 Galera集群解决了数据库高可用的问题,但是存在局限性,例如耗时的事务处理可能会导致集群性能急剧下降,甚至出现阻塞现象.而不幸的是,类似报表等业务需求就需要做 ...