C 程序集的加载和卸载

C 程序集的加载和卸载AppDomain 应用程序域 在 C 和 NET 框架中是一个非常重要的概念 它提供了一种隔离应用程序中运行的不同部分的方式

大家好,欢迎来到IT知识分享网。

目录

一、简介

二、实现程序集的加载和卸载

三、动态程序集

四、效果的实现

结束


一、简介

提到程序集的加载和卸载,不得不提到 AppDomain 对应的概念,AppDomain 在使用上,.NetFramework 和 .Net 、 .Net Core 用法也不太一样,下面是 AppDomain 的一些简介

AppDomain(应用程序域)在C#和.NET框架中是一个非常重要的概念,它提供了一种隔离应用程序中运行的不同部分的方式。每个AppDomain都是一个应用程序的轻量级容器,它为运行其中的代码提供了一个隔离的环境。这种隔离有助于减少应用程序不同部分之间的干扰,并使得应用程序的管理更加灵活和安全。

在去年我写过一篇关于 ECSharp 框架的帖子,在那个时候发就看了他加载和卸载程序集,只是都是 .Net6 的技术,.NetFramework 是用不了的,在后面我就想如何在 .NetFramework 中来实现动态更新 DLL 的技术,于是就写了这篇文章。

经常看我帖子的粉丝可能会注意到,我一直纠结如何在 Winform 上如何实现热更新技术,但是做出来的效果对我来说都不是太满意,包括之前的 自动更新(基于FTP),后面的 ECSharp 框架,和 NLua 框架,包括这次的加载和卸载DLL,甚至我还想用 Unity3d 的热更新框架 ILRuntime,配合浏览器插件 CSharpCEF,CSharpCEF 我在公司用的几个月,还是比较稳定的,但是把这么多的插件融合在一起,总体来说还是非常的麻烦的。

二、实现程序集的加载和卸载

新建一个 .NetFramework 的 Winform 项目,项目名字随意,我这里就用 HotfixTest

界面如下:

C 程序集的加载和卸载

按钮的名字我也懒的改了,就用默认的名字,具体名字是什么可以看后面的代码

新建一个类 AssemblyLoader

using System; using System.Collections.Generic; using System.Reflection; public class AssemblyLoader : MarshalByRefObject { private Assembly _assembly; private Dictionary<string, object> _classDic = new Dictionary<string, object>(); public void LoadAssembly(string path) { _assembly = Assembly.LoadFrom(path); _classDic.Clear(); } public object GetClassInstance(string typeName) { if (_assembly == null) { Console.WriteLine("[GetClassInstance]请先加载DLL"); return null; } if (_classDic.ContainsKey(typeName)) return _classDic[typeName]; var type = _assembly.GetType(typeName); if (type == null) { Console.WriteLine("[GetClassInstance]未找到当前类名:{0}", typeName); return null; } object instance = Activator.CreateInstance(type); _classDic[typeName] = instance; return instance; } public object InvokeMethod(string typeName, string methodName, object[] objects) { if (_assembly == null) { Console.WriteLine("[InvokeMethod]请先加载DLL"); return null; } var type = _assembly.GetType(typeName); if (type == null) { Console.WriteLine("[InvokeMethod]未找到当前类名:{0}", typeName); return null; } var method = type.GetMethod(methodName); if (method == null) { Console.WriteLine("[InvokeMethod]未找到当前方法:{0}", methodName); return null; } if (!method.IsStatic) { if (!_classDic.ContainsKey(typeName)) { object instance = Activator.CreateInstance(type); _classDic[typeName] = instance; return method.Invoke(instance, objects); } else { object instance = _classDic[typeName]; return method.Invoke(instance, objects); } } else //静态方法不用实例作为参数 return method.Invoke(null, objects); } }

这里使用了一个字典 _classDic 主要是用来存储反射生成的实例,目的是如果上次实例化了,下次再调用这个类中的方法,不用重复的实例化。

AssemblyLoader 继承了 MarshalByRefObject 是为了后面的程序域而做准备的。

新建一个类 AssemblyManager,从这里开始就要用到程序域了

using System; using System.IO; public class AssemblyManager { private AppDomain _appDomain; private AssemblyLoader _loader; /// <summary> /// 加载指定路径的DLL /// </summary> /// <param name="path"></param> public void LoadAssembly(string path) { if (!File.Exists(path)) { Console.WriteLine("当前DLL路径不存在"); return; } string suffix = Path.GetExtension(path); if (suffix.ToLower() != ".dll") { Console.WriteLine("当前的文件不是一个DLL"); return; } string fileName = Path.GetFileNameWithoutExtension(path); //Console.WriteLine("程序域名字:{0}", fileName); //创建一个新的程序域 _appDomain = AppDomain.CreateDomain(fileName); //在新的 AppDomain 中创建一个 AssemblyLoader 实例 string assemblyName = typeof(AssemblyLoader).Assembly.FullName; string fullName = typeof(AssemblyLoader).FullName; _loader = (AssemblyLoader)_appDomain.CreateInstanceAndUnwrap(assemblyName, fullName); _loader.LoadAssembly(path); } /// <summary> /// 执行方法 /// </summary> /// <param name="typeName">命名空间.类名</param> /// <param name="methodName">方法名</param> /// <param name="objects">参数</param> public object InvokeMethod(string typeName, string methodName, object[] objects) { return _loader?.InvokeMethod(typeName, methodName, objects); } /// <summary> /// 获取类的实例 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="typeName"></param> /// <returns></returns> public object GetClassInstance(string typeName) { return _loader?.GetClassInstance(typeName); } /// <summary> /// 卸载DLL /// </summary> public void UnloadAssembly() { if (_appDomain != null) { string domainName = _appDomain.FriendlyName; AppDomain.Unload(_appDomain); _appDomain = null; _loader = null; Console.WriteLine("程序域 {0} 已卸载,程序集已卸载", domainName); } } }

程序域相关的用法可以参考微软官方,由于这个实在是太冷门了,能查到的资料很少,估计很多公司也不会去用它,你只要知道是这么用的,不报错就行了,不用关注它内部是如何实现的。

新建一个类 Tool,这个主要是给后面 DLL 来调用的,这里也是作为一个演示,作为拓展的 DLL 调用主程序的方法也是没问题的

using System; public class Tool { public static int Add(int x, int y) { int res = x + y; Console.WriteLine("结果:{0}", res); return res; } }

三、动态程序集

动态程序集主要是用来在 Winform 运行时动态加载的 Dll 。

新建一个类库,我就用默认的名字和类好了,这里只是演示,做完了上面的工作,项目结构如下:

C 程序集的加载和卸载

在这个类库中,你可以把 HotfixTest 这个 Winform 项目添加到引用中,如果用不上那么就不用加了。

C 程序集的加载和卸载

在 Class1 中的代码如下:

using System; using System.Threading.Tasks; namespace ClassLibrary1 { public class Class1 { public string Name { get; set; } public string SetName(string name) { Name = name; Console.WriteLine("名字是:{0}", name); return "设置成功"; } public void SayHi() { Console.WriteLine("{0} 说:hello", Name); } public void StartTimer() { Swtichs(); } private bool isSw = false; public async void Swtichs() { if (isSw) return; isSw = true; while (isSw) { await Task.Delay(1000); Console.WriteLine("1"); } } public void TestAdd() { int res = Tool.Add(5, 6); Console.WriteLine("[TestAdd] res:{0}", res); } } } 

这里我并没有用静态类,或者静态方法,AssemblyLoader 类中进行反射执行时,会自动实例化,如果是静态类也没有关系,AssemblyLoader 类中,我都有做相关的判断,如下:

public object InvokeMethod(string typeName, string methodName, object[] objects) { if (_assembly == null) { Console.WriteLine("[InvokeMethod]请先加载DLL"); return null; } var type = _assembly.GetType(typeName); if (type == null) { Console.WriteLine("[InvokeMethod]未找到当前类名:{0}", typeName); return null; } var method = type.GetMethod(methodName); if (method == null) { Console.WriteLine("[InvokeMethod]未找到当前方法:{0}", methodName); return null; } if (!method.IsStatic) { if (!_classDic.ContainsKey(typeName)) { object instance = Activator.CreateInstance(type); _classDic[typeName] = instance; return method.Invoke(instance, objects); } else { object instance = _classDic[typeName]; return method.Invoke(instance, objects); } } else //静态方法不用实例作为参数 return method.Invoke(null, objects); }

在上面的方法中 method.IsStatic 是用来判断是否是静态方法,如果不是静态方法,那么就把类进行实例化,如果是静态方法,在调用时,就不必传入类的实例,如:method.Invoke(null, objects);

四、效果的实现

将 ClassLibrary1 类库生成 DLL,并将 DLL 复制到 HotfixTest 项目的 Debug 目录下(目前没用到发布模式),Debug 文件夹内文件如下:

C 程序集的加载和卸载

HotfixTest.exe 这三个文件是我运行项目后自动生成的,如果你的项目没有这三个文件可以不用管,后面运行项目后自然会有了。

Winform 界面如下:

C 程序集的加载和卸载

下面对界面中的按钮实现对应的功能

using System; using System.Windows.Forms; namespace HotfixTest { public partial class Form1 : Form { public Form1() { InitializeComponent(); } AssemblyManager manager = new AssemblyManager(); private void Form1_Load(object sender, EventArgs e) { } //加载DLL private void button1_Click(object sender, EventArgs e) { string dllPath = $"{Application.StartupPath}\\ClassLibrary1.dll"; manager.LoadAssembly(dllPath); manager.InvokeMethod("ClassLibrary1.Class1", "SetName", new object[] { "张三" }); Console.WriteLine("读取成功"); } //卸载DLL private void button2_Click(object sender, EventArgs e) { manager.UnloadAssembly(); } //调用方法1 private void button3_Click(object sender, EventArgs e) { manager.InvokeMethod("ClassLibrary1.Class1", "SayHi", null); } //调用方法2 private void button4_Click(object sender, EventArgs e) { manager.InvokeMethod("ClassLibrary1.Class1", "StartTimer", null); } //调用方法3 private void button5_Click(object sender, EventArgs e) { manager.InvokeMethod("ClassLibrary1.Class1", "TestAdd", null); } } } 

运行项目,点击加载DLL按钮

C 程序集的加载和卸载

有了正确的打印,就说明调用 DLL 中的方法成功了,继续点击 调用方法1, 调用方法2, 调用方法3,卸载DLL,效果如下:

C 程序集的加载和卸载

卸载 DLL 后,定时器也会自动停止,这时,你可以改变 Class1 内部的逻辑,重新生成后,替换 HotfixTest Debug 目录中原来的 DLL,再次加载 DLL,调用其中的方法时,你会发现,逻辑也是最新的了。

源码:点击下载

结束

如果这个帖子对你有用,欢迎 关注 + 点赞 + 留言,谢谢

end

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/126040.html

(0)
上一篇 2025-09-21 22:33
下一篇 2025-09-21 22:45

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信