cad.net dll动态加载和卸载

需求应用需求1

我们cad.net开发都会面临一个问题,加载了的dll无法实现覆盖操作,也就是cad一直打开的状态下netload两次同名但版本不一样的dll,它只会用第一次载入的.也没法做到热插拔...

应用需求2

制作一个拖拉dll到cad加载,但是不想通过发送netload到命令栏以明文形式加载...

在这两个需求之下,已有的资料 明经netloadx 似乎是不二之选...

成因

提出上面的两个需求仅仅是我为了这篇文章想为什么需要这个技术而已.......编的 ( >,< )

真正令我开始研究是因为若海提出的: 明经netloadx 在 a.dll 引用了 b.dll 时候, 为什么不会成功调用...

我首先想到是依赖,于是乎,我试图尝试直接 Assembly.Load(File.ReadAllBytes(path)) 在加载目录的每个文件,并没有报错,然后出现了一个情况,能使用单独的命令,却还是不能跨dll调用,也就是会有运行出错(runtime error).

注明: Assembly.Load(byte),转为byte是为了实现热插拔,Assembly.LoadForm()没有byte重载,也就无法拷贝到内存中去,故此不考虑.如果手写过IOC容器的,应该对以上两个函数非常的熟悉才对.

我问了群里的大佬南胜写了一篇文章回答了我,但是我用他的代码出现了几个问题:

他获取的路径是clr寻找路径之一,我需要改到加载路径上面的...这里各位可以自行去看看clr的寻找未知dll的方式.以及他只支持一个引用的dll,而我需要知道引用的引用的引用的引用的引用的引用的引用的引用的引用...的dll.

所以需要对他的代码修改一番.

工程开始项目结构

首先,一共有四个项目,

cad主插件项目:直接netload的项目.cad次插件:testa,testb [给a引用],testc [给b引用],后面还有套娃也可以...graph TDnetload命令加载-->cad主插件-->加载-->cad次插件cad次插件testA-->引用-->testB-->testC-->test....cad子插件项目testa项目代码namespace testa{ public class MyCommands { [CommandMethod("testa")] public static void testa() { Document doc = Acap.DocumentManager.MdiActiveDocument; Editor ed; if (doc != null) { ed = doc.Editor; ed.WriteMessage(" 自带函数testa."); } } [CommandMethod("gggg")] public void gggg() { Document doc = Acap.DocumentManager.MdiActiveDocument; Editor ed = doc.Editor; if (doc != null) { ed.WriteMessage(" **********gggg"); testb.MyCommands.TestBHello(); } } }}testb项目代码namespace testb{ public class MyCommands { public static void TestBHello() { Document doc = Acap.DocumentManager.MdiActiveDocument; Editor ed; if (doc != null) { ed = doc.Editor; ed.WriteMessage("************testb的Hello"); testc.MyCommands.TestcHello(); } } [CommandMethod("testb")] public static void testb() { Document doc = Acap.DocumentManager.MdiActiveDocument; Editor ed; if (doc != null) { ed = doc.Editor; ed.WriteMessage(" 自带函数testb."); } } }}testc项目代码namespace testc{ public class MyCommands { public static void TestcHello() { Document doc = Acap.DocumentManager.MdiActiveDocument; Editor ed; if (doc != null) { ed = doc.Editor; ed.WriteMessage("************testc的Hello"); } } [CommandMethod("testc")] public static void testc() { Document doc = Acap.DocumentManager.MdiActiveDocument; Editor ed; if (doc != null) { ed = doc.Editor; ed.WriteMessage(" 自带函数testc"); } } }}迭代版本号

必须更改版本号最后是*,否则无法重复加载(所有)如果想加载时候动态修改dll的版本号,需要学习PE读写.(此文略)

net framework要直接编辑项目文件.csproj,启用由vs迭代版本号:

False

然后修改AssemblyInfo.cs

net standard只需要增加.csproj的这里,没有自己加一个:

1.0.0.* 1.0.0.0 Falsecad主插件项目概念

先说一下我的测试环境和概念,

cad主插件上面写了一个命令,这个命令调用了WinForm窗体让它接受拖拽dll文件,拿到dll的路径,然后链式加载...

这个时候需要直接启动cad,然后调用netload命令加载cad主插件的dll.如果采用vs调试cad启动的话,那么我们本来也这么想的,但是会出错.经过若海两天的Debug发现了: 不能在vs调试状态下运行cad!应该直接启动它!

猜想:这个时候令vs托管了cad的内存,令所有 Assembly.Load(byte) 都进入了托管内存上面,vs自动占用到 objDebug 文件夹下的dll.,不信你也可以试一下.我开了个新文章写这个问题

启动cad之后,用命令调用出WinForm窗体,再利用拖拽testa.dll的方式,就可以链式加载到所有的dll了!

再修改testa.dll重新编译,再拖拽到WinForm窗体加载,

再修改testb.dll重新编译,再拖拽到WinForm窗体加载,

再修改testc.dll重新编译,再拖拽到WinForm窗体加载

.....如此如此,这般这般.....

WinForm窗体拖拽这个函数网络搜一下基本能搞定,我就不贴代码了,接收拖拽之后就有个testa.dll的路径,再调用传给加载函数就好了.

调用方法AssemblyDependent ad = new();List ls = new();ad.Load(item, ls);var msg = AssemblyDependent.PrintMessage(ls);if (msg != null) MessageBox.Show(msg, "加载完毕!");else MessageBox.Show("无任何信息", "加载出现问题!");链式加载

以下成员参与修改了一些bug,感谢.飞诗ひㄨㄨ那个ㄨㄨ

#define HarmonyPatch#define HarmonyPatch_1namespace IFoxCAD.LoadEx;/* * 因为此处引用了 nuget的 Lib.Harmony * 所以单独分一个工程出来作为cad工程的引用 * 免得污染了cad工程的纯洁 */#if HarmonyPatch_1[HarmonyPatch("Autodesk.AutoCAD.ApplicationServices.ExtensionLoader", "OnAssemblyLoad")]#endifpublic class AssemblyDependent : IDisposable{#if HarmonyPatch //这个是不能删除的,否则就不执行了 //HarmonyPatch hook method 返回 false 表示拦截原函数 public static bool Prefix() { return false; }#endif #region 字段和事件 /// /// 当前域加载事件,运行时出错的话,就靠这个事件来解决 /// public event ResolveEventHandler CurrentDomainAssemblyResolveEvent { add { AppDomain.CurrentDomain.AssemblyResolve += value; } remove { AppDomain.CurrentDomain.AssemblyResolve -= value; } } /// /// 拦截cad的Loader异常:默认是 /// public bool PatchExtensionLoader = false; #endregion #region 构造 /// /// 链式加载dll依赖 /// public AssemblyDependent() { //初始化一次,反复load CurrentDomainAssemblyResolveEvent += AssemblyHelper.DefaultAssemblyResolve; } #endregion #region 获取加载链 /// /// 加载程序集 /// /// dll的文件位置 /// 返回加载链 /// true字节加载,false文件加载 /// 参数 加载成功标识 /// 链条后面的不再理会,因为相同的dll引用辨识无意义 /// public bool Load(string? dllFullName, List loadStates, bool byteLoad = true) { if (dllFullName == null) throw new ArgumentNullException(nameof(dllFullName)); dllFullName = Path.GetFullPath(dllFullName);//相对路径要先转换 if (!File.Exists(dllFullName)) throw new ArgumentException("路径不存在"); //程序集数组要动态获取(每次Load的时候), //否则会变成一个固定数组,造成加载了之后也不会出现成员 var cadAssembly = AppDomain.CurrentDomain.GetAssemblies(); var cadAssemblyRef = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies(); List allRefs = new(); GetAllRefPaths(cadAssembly, cadAssemblyRef, dllFullName, allRefs); bool dllFullNameLoadOk = false; //查询加载链逆向加载,确保前面不丢失 //这里有问题,从尾巴开始的,就一定是没有任何引用吗? for (int i = allRefs.Count - 1; i >= 0; i--) { var allRef = allRefs[i]; //路径转程序集名 var an = AssemblyName.GetAssemblyName(allRef).FullName; var assembly = cadAssembly.FirstOrDefault(a => a.FullName == an); if (assembly != null) { loadStates.Add(new LoadState(allRef, false));//版本号没变不加载 continue; } //有一次true,就是true if (allRef == dllFullName) dllFullNameLoadOk = true; try { var ass = GetPdbAssembly(allRef); if (ass == null) if (byteLoad) ass = Assembly.Load(File.ReadAllBytes(allRef)); else ass = Assembly.LoadFile(allRef); loadStates.Add(new LoadState(allRef, true, ass));/*加载成功*/ } catch { loadStates.Add(new LoadState(allRef, false));/*错误造成*/ } } return dllFullNameLoadOk; } /// /// 在debug模式的时候才获取PBD调试信息 /// /// /// /// Assembly? GetPdbAssembly(string? path) {#if DEBUG //为了实现Debug时候出现断点,见

比丘资源网 » cad.net dll动态加载和卸载

发表回复

提供最优质的资源集合

立即查看 了解详情