前序
C#动态加载程序集的其中一个方法如:
string LoadDllFile = "E:\\Test\\WpfControlLibraryUnloadDll\\bin\\Release\\WpfControlLibraryUnloadDll.dll";
Assembly assembly= Assembly.LoadFrom(LoadDllFile);
加载程序集后,你想删除在电脑上的程序集文件,如:
string LoadDllFile = "E:\\Test\\WpfControlLibraryUnloadDll\\bin\\Release\\WpfControlLibraryUnloadDll.dll";
Assembly assembly= Assembly.LoadFrom(LoadDllFile);
try
{
File.Delete(LoadDllFile);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
将会得到如下的错误:
对路径“……”的访问被拒绝。
如果主程序体量比较小,不必考虑这个问题,需要更换程序集时,退出程序,重新编译程序集,重新启动即可。
但如果主程序有多层界面,不希望经常退出和启动来更换程序集,怎么办呢,如何动态加载卸载程序集呢?
程序域的相关概念
办法是使用在执行程序中新建AppDomain,在新建的AppDomain中加载和卸载Dll程序集。
AppDomain(程序域)主要作用是作为程序程序集加载的容器,包括主程序程序集、调用程序集和依赖项程序集。比如我的主程序是“WpfAppLoadUnLoadDll.exe”,可以用如下方法得到其代码所在路径:
Assembly EntryAssembly = Assembly.GetEntryAssembly();
MessageBox.Show(EntryAssembly.CodeBase);
获取当前域的名称的代码为:
AppDomain CurrentDomain = AppDomain.CurrentDomain;
MessageBox.Show(CurrentDomain.FriendlyName);
FriendlyName:程序域的名称。
执行下面的代码,将会有以下信息提示:
主程序域是不能卸载的,所以要在主程序域中新建域。
执行下面的代码:
Assembly ExecutingAssembly = Assembly.GetExecutingAssembly();
string LoadDllFile = "E:\\Test\\WpfControlLibraryUnloadDll\\bin\\Release\\WpfControlLibraryUnloadDll.dll";
AppDomain LoadDllDomain = AppDomain.CreateDomain("LoadDllDomain");
Assembly assembly = Assembly.LoadFrom(LoadDllFile);
AppDomain.Unload(LoadDllDomain);
try
{
File.Delete(LoadDllFile);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
AppDomain.CreateDomain("LoadDllDomain"):在主程序域中创建名为LoadDllDomain的新域。
AppDomain.Unload(LoadDllDomain):在新域中加载程序集Dll后卸载新域。
执行代码,依然会提示删除被拒绝。
动态加载卸载程序集
代码
把代码改为以下(.net framework wpfApp主窗体代码):
using System;
using System.Windows;
using System.Reflection;
using System.Runtime.Remoting;
using System.IO;
namespace WpfAppLoadUnLoadDll
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Assembly ExecutingAssembly = Assembly.GetExecutingAssembly();
AppDomain LoadDllDomain = AppDomain.CreateDomain("LoadDllDomain");
ObjectHandle objectHandle = LoadDllDomain.CreateInstance(ExecutingAssembly.FullName, typeof(ProxyObject).FullName);
object obj = objectHandle.Unwrap();
ProxyObject proxyObject = (ProxyObject)obj;
string LoadDllFile = "E:\\Test\\WpfControlLibraryUnloadDll\\bin\\Release\\WpfControlLibraryUnloadDll.dll";
proxyObject.LoadAssembly(LoadDllFile);
AppDomain.Unload(LoadDllDomain);
try
{
File.Delete(LoadDllFile);
MessageBox.Show("LoadDllFile文件已删除");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
class ProxyObject : MarshalByRefObject
{
public void LoadAssembly(string LoadDllFile)
{
Assembly.LoadFile(LoadDllFile);
}
}
}
代码解释
LoadDllDomain.CreateInstance:在程序域LoadDllDomain中创建实例,即创建ProxyObject这个类的实例。第一个参数是程序集的显示名,如ExecutingAssembly.FullName(全名),是以下字串:
另外用程序集标题也可以,如下:
但不能是"WpfAppLoadUnLoadDll.exe"。
typeof(ProxyObject).FullName:在这里是ProxyObject这个类的全名,即所在命名空间+类的形式,等同于:
WpfAppLoadUnLoadDll.ProxyObject
ObjectHandle objectHandle:System.Runtime.Remoting.ObjectHandle类,即远程类,远程调用。那AppDomain.CreateInstance方法返回的就是远程对象。
object obj = objectHandle.Unwrap():返回包装的对象,将远程对象objectHandle转为对象。
ProxyObject proxyObject = (ProxyObject)obj:实际上obj属于ProxyObject 类,所以将其还原为的ProxyObject 类对象。因为ProxyObject 继承了MarshalByRefObject类,能跨程序域访问。
LoadAssembly:在新域中加载程序集。
过程就是这样理解:查找当前程序集信息,在当前程序中中查找到ProxyObject 这个类在程序集中的位置,在新域中创建ProxyObject 远程对象实例,该实例具有远程特征(即和当前域隔离),用这个远程对象实例来调用程序集,就能实现程序集的动态加载和卸载。结果如下:
调用程序域中的程序集所包含的方法
假设WpfControlLibraryUnloadDll.dll是一个用户控件类的程序集,MainWindow.xaml中代码如下:
<UserControl x:Class="WpfControlLibraryUnloadDll.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<StackPanel>
<TextBlock Text="用户控件"></TextBlock>
</StackPanel>
</UserControl>
UserControl1.xaml.cs文档的代码如下:
using System.Windows.Controls;
namespace WpfControlLibraryUnloadDll
{
/// <summary>
/// UserControl1.xaml 的交互逻辑
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public int Count(int a,int b)
{
return a + b;
}
}
}
当前执行程序的MainWindow.cs的代码如下:
using System;
using System.Windows;
using System.Reflection;
using System.Runtime.Remoting;
using System.IO;
namespace WpfAppLoadUnLoadDll
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Assembly ExecutingAssembly = Assembly.GetExecutingAssembly();
AppDomain LoadDllDomain = AppDomain.CreateDomain("LoadDllDomain");
ObjectHandle objectHandle = LoadDllDomain.CreateInstance(ExecutingAssembly.FullName, typeof(ProxyObject).FullName);
object obj = objectHandle.Unwrap();
ProxyObject proxyObject = (ProxyObject)obj;
string LoadDllFile = "E:\\Test\\WpfControlLibraryUnloadDll\\bin\\Release\\WpfControlLibraryUnloadDll.dll";
proxyObject.LoadAssembly(LoadDllFile);
int returnInt= proxyObject.Invoke("WpfControlLibraryUnloadDll.UserControl1", "Count", new object[] { 1, 2 });
MessageBox.Show(returnInt.ToString());
AppDomain.Unload(LoadDllDomain);
try
{
File.Delete(LoadDllFile);
MessageBox.Show("LoadDllFile文件已删除");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
class ProxyObject : MarshalByRefObject
{
Assembly assembly = null;
public void LoadAssembly(string LoadDllFile)
{
assembly = Assembly.LoadFrom(LoadDllFile);
}
public int Invoke(string fullClassName, string methodName, params object[] args)
{
try
{
Type type = assembly.GetType(fullClassName);
MethodInfo method = type.GetMethod(methodName);
object obj = Activator.CreateInstance(type);
object InvokeObject= method.Invoke(obj, args);
return (int)InvokeObject;
}
catch
{
return int.MinValue;
}
}
}
}
public int Invoke:ProxyObject类中的方法,作用是反射在程序域中加载的dll中所含的方法Count,并执行获取执行结果。运行主窗体程序,其结果如下:
在远程代理ProxyObject中返回数据可以,但要返回用户控件就没有办法了,因为UserContro很难序列化为可远程传送的数据,远程访问的数据又必须要求序列化,不能是控件对象本身。
因此动态加载卸载程序集一般是类库的程序集,不能是用户控件类程序集。
注意:AppDomain.CreateInstance和Activator.CreateInstance两者返回的类型不同。前者返回的是远程类对象,即System.Runtime.Remoting.ObjectHandle,而后者返回的是object。
注意,在这里使用CreateInstance,而不是CreateInstanceFrom。CreateInstanceFrom要求第一个参数是程序集的文件名,即在电脑上的程序集Dll路径,如果是这样,应该改写为以下的调用方式:
Assembly ExecutingAssembly = Assembly.GetExecutingAssembly();
ObjectHandle objectHandle = LoadDllDomain.CreateInstanceFrom(ExecutingAssembly.Location, typeof(ProxyObject).FullName);
ExecutingAssembly.Location:当前执行程序在电脑上的路径,如下格式:
E:\Test\WpfAppLoadUnLoadDll\bin\Release\WpfAppLoadUnLoadDll.exe
可以看出CreateInstance和CreateInstanceFrom所需要的程序集参数不同,前者需要程序集显示名(assemblyName),后者需要程序集的加载路径(assemblyFile)。运用于不同的需要场合。
方法封装
另外建一个类库项目,名为ClassLibraryLoadDll,编译输出为“ClassLibraryLoadDll.dll”。在其文档中有一个类:
using System;
namespace ClassLibraryLoadDll
{
public class Class1
{
public static int Count(int a,int b)
{
return a + b;
}
}
}
那加载和卸载该程序集的代码就变为(.net framework wpfApp主窗体代码):
using System;
using System.Windows;
using System.Reflection;
using System.Runtime.Remoting;
using System.IO;
namespace WpfAppLoadUnLoadDll
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Assembly ExecutingAssembly = Assembly.GetExecutingAssembly();
AppDomain LoadDllDomain = AppDomain.CreateDomain("LoadDllDomain");
ObjectHandle objectHandle = LoadDllDomain.CreateInstance(ExecutingAssembly.FullName, typeof(ProxyObject).FullName);
object obj = objectHandle.Unwrap();
ProxyObject proxyObject = (ProxyObject)obj;
string LoadDllFile = "E:\\Test\\ClassLibraryLoadDll\\bin\\Release\\ClassLibraryLoadDll.dll";
proxyObject.LoadAssembly(LoadDllFile);
object returnObject = proxyObject.Invoke("ClassLibraryLoadDll.Class1", "Count", new object[] { 1, 2 });
MessageBox.Show(returnObject.ToString());
AppDomain.Unload(LoadDllDomain);
try
{
File.Delete(LoadDllFile);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
class ProxyObject : MarshalByRefObject
{
Assembly assembly = null;
public void LoadAssembly(string LoadDllFile)
{
assembly = Assembly.LoadFrom(LoadDllFile);
}
public object Invoke(string fullClassName, string methodName, params object[] args)
{
try
{
Type type = assembly.GetType(fullClassName);
MethodInfo method = type.GetMethod(methodName);
object obj = Activator.CreateInstance(type);
object InvokeObject = method.Invoke(obj, args);
return InvokeObject;
}
catch
{
return null;
}
}
}
}
这样去动态加载卸载程序集即可。
还可以封装成一个类,如下代码:
using System;
using System.Reflection;
using System.Runtime.Remoting;
namespace WpfAppLoadUnLoadDll
{
internal class AssemblyLoadUnload
{
private AppDomain LoadDllDomain;
private ProxyObject proxyObject;
public void Load(string LoadDllFile)
{
Assembly ExecutingAssembly = Assembly.GetExecutingAssembly();
LoadDllDomain = AppDomain.CreateDomain("LoadDllDomain");
ObjectHandle objectHandle = LoadDllDomain.CreateInstance(ExecutingAssembly.FullName, typeof(ProxyObject).FullName);
object obj = objectHandle.Unwrap();
proxyObject = (ProxyObject)obj;
proxyObject.LoadAssembly(LoadDllFile);
}
public object GetData(string typeFullName, string invokeMethodName, object[] deliverData)
{
object returnObject = proxyObject.Invoke(typeFullName, invokeMethodName, deliverData);
return returnObject;
}
public void UnLoad()
{
AppDomain.Unload(LoadDllDomain);
}
}
class ProxyObject : MarshalByRefObject
{
Assembly assembly = null;
public void LoadAssembly(string LoadDllFile)
{
assembly = Assembly.LoadFrom(LoadDllFile);
}
public object Invoke(string fullClassName, string methodName, params object[] args)
{
try
{
Type type = assembly.GetType(fullClassName);
MethodInfo method = type.GetMethod(methodName);
object obj = Activator.CreateInstance(type);
object InvokeObject = method.Invoke(obj, args);
return InvokeObject;
}
catch
{
return null;
}
}
}
}
调用方法(.net framework wpfApp主窗体代码):
using System;
using System.Windows;
using System.Reflection;
using System.Runtime.Remoting;
using System.IO;
namespace WpfAppLoadUnLoadDll
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
WpfAppLoadUnLoadDll.AssemblyLoadUnload assemblyLoadUnload=new AssemblyLoadUnload();
assemblyLoadUnload.Load("G:\\IGXU\\CsProg\\CsFrame\\Test\\ClassLibraryLoadDll\\bin\\Release\\ClassLibraryLoadDll.dll");
object obj= assemblyLoadUnload.GetData("ClassLibraryLoadDll.Class1", "Count", new object[] { 1, 2 });
MessageBox.Show(obj.ToString());
assemblyLoadUnload.UnLoad();
}
}
}
结语
动态加载Dll方法基本方法是新建一个程序域,然后用方法AppDomain.CreateInstance生成远程对象,通过远程代理在远程类中加载程序集dll,在需要的时候卸载程序域。之后就可以生成新的dll,重新加载该dll。
由于程序域之间是隔离的,类似于远程访问,传送数据需要序列化数据,因此该方法不能应用于动态加载或卸载控件。