C# 动态加载卸载程序集

前序

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。

由于程序域之间是隔离的,类似于远程访问,传送数据需要序列化数据,因此该方法不能应用于动态加载或卸载控件。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值