完整的动态加载/卸载程序集的解决方案
转自:http://dlwang2002.cnblogs.com/archive/2005/10/31/265810.html
实现目标
1:所加载的dll分布在不同的文件夹下,可以不再运行目录bin下。以创建AppDomain的方式加载/卸载
2:运行中可以自动监测dll的版本,如果dll又更新,则自动卸载原来的dll,重新加载新的程序集(当然也就得必须可以替换正在运行中的dll)
3:加载程序集中的类可以访问主程序域的方法(主程序域中的类当然可以访问自程序域中的实例的方法)
优点:
1:可以在运行中替换具体实现,不需要停止程序再加载
2:在不同程序域里的实现互相隔离,代码运行比较安全,一个实现出了问题,可以只卸载那一个AppDomain就可以
几个问题:
1:子程序域中无法访问主程序域中的一些配置文件,比如app.config
2:运行速度会慢一些,因为需要反复的 序列化/反序列化 。
3:调试复杂,不同程序域的调试就像递归函数一样,难以调试
4:不再是传实例引用,所有的都是传值的方式。
实现思路:
1:通过创建不同的AppDomain来加载dll,通过卸载AppDomain来卸载dll
2:通过一个Proxy类(继承自MarshalByRefObject)来访问具体的实现,一定不能返回具体实现的实例,而是要通过传递参数,在代理中执行,然后返回结果
3:子程序域中实现通过一个ProxyBack(MarshalByRefObject)的代理来访问主程序域中的方法,这个ProxyBack使用TCP信道来通讯 (Microsoft .Net Remoting)
4:所有需要在不同域之间传递的参数/返回值 都必须是可以序列化的。
5:制作一个DLLWatcher来监视dll所在文件夹的状态
具体实现:
1:代理类 创建不同的程序域 访问子程序域中的方法
http://dlwang2002.cnblogs.com/archive/2005/10/18/257425.html
2:使用Microsoft .Net Remoting技术,制作ProxyBack
关于Microsoft .Net Remoting技术,Wayfarer's Prattle有很详细的解释http://www.cnblogs.com/wayfarer/archive/2004/07/30/28723.html
这里主要看这几个主要的实现和注意事项
这是代理的Server端,在乎程序域中启动,侦听
public ProxyBackServer()
{
if(!isStart)
{
TcpChannel chan = new TcpChannel(8085);
ChannelServices.RegisterChannel(chan);
RemotingConfiguration.RegisterWellKnownServiceType(typeof( xxx.ProxyBackServer),
"CallLocalSM",WellKnownObjectMode.SingleCall);
isStart=true;
}
方法:注意所传递的都必须是可以序列化的对象,在需要加载dll的文件夹下也要有这个传递对象实现的程序集。
public IStateDictionary RunService(IStateDictionary request)
在这个Server的析构函数中要释放这个TCP信道
~ProxyBackServer()
// {
// IChannel[] channels = ChannelServices.RegisteredChannels;
// //关闭指定名为CallLocalSM的通道;
// foreach (IChannel eachChannel in channels)
// {
// if (eachChannel.ChannelName == "CallLocalSM")
// {
// TcpChannel tcpChannel = (TcpChannel)eachChannel;
//
// //关闭监听;
// tcpChannel.StopListening(null);
//
// //注销通道;
// ChannelServices.UnregisterChannel(tcpChannel);
// }
// }
//
// }
这是代理的Client端,子程序域访问主程序域的时候使用
public ProxyBackClient()
{
if(!isReg)// boot once
{
TcpChannel chan = new TcpChannel();
ChannelServices.RegisterChannel(chan);
isReg=true;
}
_server = (ProxyBackServer)Activator.GetObject(
typeof(xxxx.ProxyBackServer), "tcp://localhost:8085/CallLocalSM");
if (_server == null)
{
throw new Exception("can not connect to local host to get instance of sm");
}
}
public IContext RunService(IContext ctx)
{
try
{
ctx.Response= this._server.RunService(ctx.Request);
return ctx;
..........
}
}
3:DllWatcher
............
_mask = "*.dll";
// initialize watcher to watch the process directory
this.dllWatcher = new FileSystemWatcher();
this.dllWatcher .Path = _path;
this.dllWatcher .Filter = _mask;
this.dllWatcher .Changed += new FileSystemEventHandler(File_OnChanged);
this.dllWatcher .Created += new FileSystemEventHandler(File_OnChanged);
this.dllWatcher .Deleted += new FileSystemEventHandler(File_OnChanged);
// tell it to start watching
this.dllWatcher .EnableRaisingEvents = true;
在接收到事件之后,调用主域中的方法卸载AppDomain,然后重新加载 就可以