cad.net dll动态加载和卸载
20250320
不应该loadx时候采用链式加载,而是loadx命令只加载目标dll,
每个加载的dll应该独立程序域.
如果加入了同名不同版本的,就可以自动卸载之前版本的.
当触发需要之前的版本时候,通过事件加载回来.
我实现在此处:
插件式架构最小实现:
https://www.cnblogs.com/JJBox/p/18800009
下面文章为旧文,内容存在错误,
例如我把Assembly类传输了,这是一个错误,跨域只能传输值类型,
请用上文最小实现.
_
_
_
_
_
_
_
需求
1: 我们cad.net开发都会面临一个问题,
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重载,
也就无法拷贝到内存中去,故此不考虑.

南胜写了一篇文章回答了,
但是只支持一个引用的dll,而我需要知道引用的引用...的dll.
所以对他的代码修改一番.
工程开始
项目地址
目前博客比较新
首先,共有四个项目.
- cad主插件项目:直接netload的项目.
- cad次插件:testa,testb [给a引用],testc [给b引用],后面还有套娃也可以...
cad子插件项目
// testa项目代码
namespace LoadTesta {
public class MyCommands {
[CommandMethod(nameof(testa))]
public static void testa() {
var doc = Acap.DocumentManager.MdiActiveDocument;
if (doc is null) return;
doc.Editor.WriteMessage("\r\n 自带函数" + mameof(testa));
}
[CommandMethod(nameof(gggg))]
public void gggg() {
var doc = Acap.DocumentManager.MdiActiveDocument;
if (doc is null) return;
doc.Editor.WriteMessage("\r\n ****" + nameof(gggg));
testb.MyCommands.TestBHello();
}
}
}
// testb项目代码
namespace LoadTestb {
public class MyCommands {
public static void TestBHello() {
var doc = Acap.DocumentManager.MdiActiveDocument;
if (doc is null) return;
doc.Editor.WriteMessage("\r\n ****" + nameof(TestBHello));
testc.MyCommands.TestcHello();
}
[CommandMethod(nameof(testb))]
public static void testb() {
var doc = Acap.DocumentManager.MdiActiveDocument;
if (doc is null) return;
doc.Editor.WriteMessage("\r\n 自带函数" + nameof(testb));
}
}
}
// testc项目代码
namespace LoadTestc {
public class MyCommands {
public static void TestCHello() {
var doc = Acap.DocumentManager.MdiActiveDocument;
if (doc is null) return;
doc.Editor.WriteMessage("\r\n ****" + nameof(TestCHello));
}
[CommandMethod(nameof(testc))]
public static void testc() {
var doc = Acap.DocumentManager.MdiActiveDocument;
if (doc is null) return;
doc.Editor.WriteMessage("\r\n 自带函数" + nameof(testc));
}
}
}
迭代版本号
必须更改版本号最后是*,否则无法重复加载(所有)
如果想加载时候动态修改dll的版本号,需要学习PE读写.(此文略)
net framework要直接编辑项目文件.csproj,启用由vs迭代版本号:
<PropertyGroup>
<Deterministic>False</Deterministic>
</PropertyGroup>
然后修改AssemblyInfo.cs

net standard只需要增加.csproj的这里,没有自己加一个:
<PropertyGroup>
<AssemblyVersion>1.0.0.*</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<Deterministic>False</Deterministic>
</PropertyGroup>
cad主插件项目
概念
先说一下测试环境和概念,
cad主插件上面写一个命令,
调用了WinForm窗体让它接受拖拽dll文件,获取dll路径加载.
也可以简化,做一个loadx命令,获取dll路径加载.
这个时候需要直接启动cad,
然后调用netload命令加载cad主插件的dll.
如果采用vs调试cad启动的话还是会出错.
经过若海两天的Debug发现了: 不能在vs调试状态下运行cad!应该直接启动它!
猜想:这个时候令vs令所有 Assembly.Load(byte) 都进入了vs内存上面,
vs自动占用到 obj\Debug 文件夹下的dll.
我开了个新文章写这个问题
启动cad之后,用命令调用出WinForm窗体,
再利用拖拽testa.dll的方式,就可以链式加载到所有的dll了!
再修改testa.dll重新编译,再拖拽到WinForm窗体加载,
再修改testb.dll重新编译,再拖拽到WinForm窗体加载,
再修改testc.dll重新编译,再拖拽到WinForm窗体加载
...如此如此,这般这般...
WinForm窗体拖拽这个函数网络搜一下基本能搞定,我就不贴代码了,
接收拖拽之后就有个testa.dll的路径,再调用传给加载函数就好了.
获取依赖链并加载
因为此处引用了 nuget 的 Lib.Harmony
所以单独分一个工程出来作为cad工程的引用
免得污染了cad工程的纯洁
#define HarmonyPatch
#define HarmonyPatch_1
namespace IFoxCAD.LoadEx;
#if HarmonyPatch_1
[HarmonyPatch("Autodesk.AutoCAD.ApplicationServices.ExtensionLoader", "OnAssemblyLoad")]
#endif
public class AssemblyDependent : IEnumerable<Assembly> {
#if HarmonyPatch
// 这个是不能删除的,否则就不执行了
// HarmonyPatch hook method 返回 false 表示拦截原函数
public static bool Prefix() { return false; }
#endif
/// <summary>
/// 拦截cad的Loader异常:默认是<paramref name="false"/>
/// </summary>
public bool PatchExtensionLoader = false;
/// <summary>
/// 当前程序域运行时解析事件,运行时缺失查找加载路径
/// </summary>
public event ResolveEventHandler AssemblyResolve {
add { AppDomain.CurrentDomain.AssemblyResolve += value; }
remove { AppDomain.CurrentDomain.AssemblyResolve -= value; }
}
/// <summary>
/// 当前程序域仅反射解析事件,运行时缺失查找加载路径
/// </summary>
public event ResolveEventHandler ReflectionOnlyAssemblyResolve {
add { AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += value; }
remove { AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= value; }
}
/// <summary>
/// 加载成功的程序集
/// 成功的会被程序域持有
/// 此处有路径索引,防止卸载时候有强引用造成卸载失败
/// </summary>
public Dictionary<string, WeakReference<Assembly>> LoadMap = new(StringComparer.OrdinalIgnoreCase);
Dictionary<string, string> LoadMapError = new();
// 迭代器
public IEnumerator<Assembly> GetEnumerator()
// 先收集无效键,再移除.
var invalidKeys = _adep.LoadMap
.Where(pair => !pair.Value.TryGetTarget(out _))
.Select(pair => pair.Key)
.ToList();
foreach (var key in invalidKeys)
_adep.LoadMap.Remove(key);
// 返回剩余的有效程序集
foreach (var pair in _adep.LoadMap) {
if (pair.Value.TryGetTarget(out var assembly) && assembly != null)
yield return assembly;
}
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
/// <summary>
/// 加载程序集
/// </summary>
/// <param name="dllFile">dll的文件位置</param>
/// <param name="byteLoad">true字节加载,false文件加载</param>
/// <returns>参数加载成功标识<paramref name="dllFile"/></returns>
[MethodImpl(MethodImplOptions.NoInlining)]
public bool Load(string dllFile, bool byteLoad = true) {
if (dllFile is null)
throw new ArgumentNullException(nameof(dllFile));
// 相对路径要先转换
dllFile = Path.GetFullPath(dllFile);
if (!File.Exists(dllFile))
throw new ArgumentException("路径不存在");
// 获取加载链,Location不一定存在
// FullName: System.ComponentModel.Primitives, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// Location: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.0\System.ComponentModel.Primitives.dll
HashSet<string> dllFileSet = new(StringComparer.OrdinalIgnoreCase);
var cadAssembly = AppDomain.CurrentDomain.GetAssemblies()
.ToDictionary(ass => ass.FullName, ass => ass, StringComparer.OrdinalIgnoreCase);
var cadAssemblyRef = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies()
.ToDictionary(ass => ass.FullName, ass => ass, StringComparer.OrdinalIgnoreCase);
GetDependencyLink(cadAssembly, cadAssemblyRef, dllFile, dllFileSet);
// 加载链进行加载
foreach(var mfile in dllFileSet) {
// 名称和版本号完全一致,不进行加载
if (cadAssembly.ContiansKey(AssemblyName.GetAssemblyName(mfile).FullName)) {
LoadMapError[mfile] = "版本号已经存在";
continue;
}
try {
var ass = GetPdbAssembly(mfile);
ass ??= byteLoad
? Assembly.Load(File.ReadAllBytes(mfile))
: Assembly.LoadFile(mfile);
LoadMap[mfile] = ass;
} catch(Exception ex) { LoadMapError[mfile] = "{ex.Message}"; }
}
return LoadMap.ContainsKey(dllFile);
}
// 为了实现Debug时候出现断点
// https://www.cnblogs.com/DasonKwok/p/10510218.html
// https://www.cnblogs.com/DasonKwok/p/10523279.html
/// <summary>
/// 在debug模式的时候才获取PBD调试信息
/// </summary>
/// <param name="path"></param>
/// <param name="byteLoad"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.NoInlining)]
[Conditional("DEBUG")]
Assembly? GetPdbAssembly(string file) {
var dir = Path.GetDirectoryName(file);
var pdbName = Path.GetFileNameWithoutExtension(file);
var pdbFile = Path.Combine(dir, $"{pdbName}.pdb");
if (!File.Exists(pdbFile)) return null;
return Assembly.Load(File.ReadAllBytes(file), File.ReadAllBytes(pdbFile));
}
/// <summary>
/// 递归获取加载链
/// </summary>
/// <param name="cadAssembly">程序集运行解析</param>
/// <param name="cadAssemblyRef">程序集反射解析</param>
/// <param name="dllFile">dll文件位置</param>
/// <param name="dllFileSet">dll文件集合返回用</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.NoInlining)]
void GetDependencyLink(
Dictionary<string, Assembly> cadAssembly,
Dictionary<string, Assembly> cadAssemblyRef,
string dllFile, HashSet<string> dllFileSet) {
if (dllFile is null)
throw new ArgumentNullException(nameof(dllFile));
if (!File.Exists(dllFile)) return;
if (!dllFileSet.Add(dllFile)) return;
// 路径转程序集名
var assName = AssemblyName.GetAssemblyName(dllFile).FullName;
Assembly assRef;
// 运解和反解都没有,
// 就把dll加载到反解以及更新缓存,用来找依赖路径,
if (!cadAssembly.TryGetValue(assName, out assRef)) {
if (!cadAssemblyRef.TryGetValue(assName, out assRef)) {
assRef = TryOnlyLoad(dllFile);
if (assRef is not null) {
if (assName != assRef.FullName) throw new("理论上它们是一样");
cadAssemblyRef[assRef.FullName] = assRef;
}
}
}
if (assRef is null) return;
// 递归获取依赖路径
// 拖拽加载dll的目录,过滤得到为程序集名的dll.
var dir = Path.GetDirectoryName(dllFile);
var files = assRef.GetReferencedAssemblies()
.Select(ass => Path.Combine(dir, $"{ass.Name}.dll"))
.Where(file => !dllFileSet.Contains(file) && File.Exists(file))
.ToArray();
foreach(var file in files) {
GetDependencyLink(cadAssembly, cadAssemblyRef, file, dllFileSet);
}
}
/// <summary>
/// 反射解析加载文件
/// </summary>
/// <param name="dllFile">dll文件位置</param>
/// <returns>反射解析内的程序集</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
Assembly? TryOnlyLoad(string dllFile) {
try {
var byteRef = File.ReadAllBytes(dllFile);
// 若用名称参数,没有依赖会报错,所以用字节参数.
// 不能重复加载同一个dll,用hashset排除.
if (!PatchExtensionLoader) {
return Assembly.ReflectionOnlyLoad(byteRef);
}
#if HarmonyPatch_1
// QQ1548253108: 我这里会报错,提供了解决方案.
// 方案一: 在类上面加 [HarmonyPatch("Autodesk.AutoCAD.ApplicationServices.ExtensionLoader", "OnAssemblyLoad")]
const string ext = "Autodesk.AutoCAD.ApplicationServices.ExtensionLoader";
Harmony hm = new(ext);
hm.PatchAll();
result = Assembly.ReflectionOnlyLoad(byteRef);
hm.UnpatchAll(ext);
#endif
#if HarmonyPatch_2
// 方案二:跟cad耦合了
const string ext = "Autodesk.AutoCAD.ApplicationServices.ExtensionLoader";
var docAss = typeof(Autodesk.AutoCAD.ApplicationServices.Document).Assembly;
var a = docAss.GetType(ext);
var b = a.GetMethod("OnAssemblyLoad");
Harmony hm = new(ext);
hm.Patch(b, new HarmonyMethod(GetType(), "Dummy"));
result = Assembly.ReflectionOnlyLoad(byteRef);
hm.UnpatchAll(ext);
#endif
return result;
} catch { return null; }
}
/// <summary>
/// 加载信息
/// </summary>
public string PrintMessage() {
var sb = new StringBuilder();
foreach (var pair in LoadMap) {
sb.AppendLine($"++ {pair.Key}");
}
if (sb.Count > 0)
sb.Insert(0, "** 这些文件加载成功:");
int num = sb.Length;
foreach (var pair in LoadMapError) {
sb.AppendLine($"-- {pair.Key}, 错误消息:{pair.Value}");
}
if (sb.Length > num)
sb.Insert(num, "** 这些文件已被加载过,同时重复名称和版本号,跳过:");
return sb.ToString();
}
}
运行域事件
最重要是这个事件,
它会在运行的时候找已经载入内存上面的程序集.
关于AppDomain.CurrentDomain.AssemblyResolve事件,
它用于在程序集加载失败时重新加载程序集,
通常用于动态加载程序集的场景,
你可以通过该事件自定义程序集的查找和加载逻辑,确保程序集能够正确加载.
0x01 动态加载要注意所有的引用外的dll的加载顺序
0x02 指定版本: Acad2008若没有这个事件.
1,动态编译的命令执行时候无法引用当前的程序集函数,弹出一个报错.
2,动态编译之后要加载程序域内,就能实现触发默认检索机制,不需要这个事件.
所以这是两种方案,事件进行动态检索可以动态更换程序域顺序,从而更换dll.
0x03 目录构成: 动态加载时,dll的地址会在系统的动态目录里,而它所处的程序集(运行域)是在动态目录里.
0x04 命令构成: cad自带的netload会把所处的运行域给改到cad自己的,而动态加载不通过netload,所以要自己去改.
调用
AppDomain.CurrentDomain.AssemblyResolve += AssemblyHelper.AssemblyResolve;
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += AssemblyHelper.ReflectionOnlyAssemblyResolve;
封装
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.IO;
using System.Linq;
using System.Configuration;
namespace IFoxCAD.LoadEx;
public class AssemblyHelper {
// 仅反射加载时解析程序集,不触发静态构造函数等
[MethodImpl(MethodImplOptions.NoInlining)]
public static Assembly? ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs e)
{
var cadAss = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();
return Resolve(cadAss, sender, e);
}
// 常规程序集解析失败处理
[MethodImpl(MethodImplOptions.NoInlining)]
public static Assembly? AssemblyResolve(object sender, ResolveEventArgs e)
{
var cadAss = AppDomain.CurrentDomain.GetAssemblies();
return Resolve(cadAss, sender, e);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static Assembly? Resolve(Assembly[] cadAss, object sender, ResolveEventArgs e)
{
// 精确匹配全名(名称/版本/公钥等)
var exactMatch = cadAss.FirstOrDefault(ass => e.Name == ass.FullName);
if (exactMatch != null) return exactMatch;
// 解析请求的程序集基本信息
var requestedName = new AssemblyName(e.Name);
// 模糊匹配: 名称相同且版本最高的程序集
var highestVersionMatch = cadAss
.Where(ass => ass.GetName().Name == requestedName.Name)
.OrderByDescending(ass => ass.GetName().Version)
.FirstOrDefault();
if (highestVersionMatch != null) return highestVersionMatch;
// 尝试在文件系统中查找程序集
string? assemblyPath = FindAssemblyPath(e.Name);
if (!string.IsNullOrEmpty(assemblyPath) && File.Exists(assemblyPath))
return Assembly.LoadFrom(assemblyPath);
// 处理资源程序集找不到的情况(参考:https://stackoverflow.com/a/43818961)
if (requestedName.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)
&& requestedName.CultureInfo?.IsNeutralCulture == false)
return null;
// 记录详细的错误信息
var errorLog = new StringBuilder();
errorLog.AppendLine($"程序集解析失败 [{DateTime.Now:yyyy-MM-dd HH:mm:ss}]");
errorLog.AppendLine($"请求的程序集全名: {e.Name}");
errorLog.AppendLine($"已加载的程序集列表({cadAss.Length}个):");
foreach (var ass in cadAss.OrderBy(a => a.FullName))
errorLog.AppendLine($" - {ass.FullName}");
Trace.TraceError(errorLog.ToString());
// 调试时中断,发布环境建议记录日志后终止
Debugger.Break();
return null;
}
private static string? FindAssemblyPath(string assemblyFullName) {
var requestedName = new AssemblyName(assemblyFullName);
string[] searchPaths = {
AppDomain.CurrentDomain.BaseDirectory,
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins"),
ConfigurationManager.AppSettings["CustomAssemblyPath"],
@"C:\Program Files\IFoxCAD\Assemblies"
};
// 查找程序集路径
var foundPath = searchPaths
.Where(Directory.Exists) // 过滤存在的目录
.Select(dir => Path.Combine(dir, $"{requestedName.Name}.dll")) // 生成目标路径
.FirstOrDefault(File.Exists); // 返回第一个存在的文件路径
// 如果找到路径,直接返回
if (foundPath != null) return foundPath;
// WPF 程序集特殊处理(调试时使用本地版本)
if (Debugger.IsAttached && requestedName.Name.StartsWith("PresentationCore")) {
string wpfLocation = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
@"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8",
$"{requestedName.Name}.dll");
if (File.Exists(wpfLocation)) return wpfLocation;
}
// 如果都找不到
return null;
}
// 从嵌入资源加载程序集(适用于单文件发布)
private static Assembly? OnResolveAssembly(object sender, ResolveEventArgs args)
{
var resourceName = $"{new AssemblyName(args.Name).Name}.dll";
// 精确匹配资源名称(考虑命名空间路径)
var fullResourceName = Assembly.GetExecutingAssembly()
.GetManifestResourceNames()
.FirstOrDefault(name => name.EndsWith(resourceName, StringComparison.OrdinalIgnoreCase));
if (fullResourceName is null) return null;
using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(fullResourceName);
if (stream is null) return null;
byte[] buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
return Assembly.Load(buffer);
}
}
// Acad21+vs22 容易触发这个资源的问题
// https://stackoverflow.com/questions/4368201
// if (an.Name.EndsWith(".resources") && !an.CultureName.EndsWith("neutral"))
// return null;
// Process.GetCurrentProcess().Kill();
动态编译
using System;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
public class DynamicCompiler {
public static void Main() {
// 动态编译代码
string code = @"
using System;
public class HelloWorld {
public static void SayHello() {
Console.WriteLine(""Hello, World!"");
}
}";
var provider = new CSharpCodeProvider();
var compilerParams = new CompilerParameters
{
GenerateInMemory = true, // 在内存中生成程序集
ReferencedAssemblies = { "System.dll" } // 添加引用
};
CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, code);
if (results.Errors.HasErrors)
{
foreach (var error in results.Errors)
Console.WriteLine(error.ToString());
return;
}
// 显式加载生成的程序集,就不用使用事件了.
var compiledAssembly = results.CompiledAssembly;
AppDomain.CurrentDomain.Load(compiledAssembly.GetName());
// 调用动态编译的方法
var type = compiledAssembly.GetType("HelloWorld");
var method = type.GetMethod("SayHello");
method.Invoke(null, null);
}
}
新程序域并执行代码
// 可跨应用程序域调用的类
public class RemoteObject : MarshalByRefObject {
public void SayHello() {
Console.WriteLine("Hello from " + AppDomain.CurrentDomain.FriendlyName);
}
}
class Program {
static void Main() {
// 创建新的应用程序域
AppDomain newDomain = AppDomain.CreateDomain("NewDomain");
// 在新的应用程序域中执行代码
newDomain.DoCallBack(() => {
Console.WriteLine("Executing in new AppDomain: " + AppDomain.CurrentDomain.FriendlyName);
});
// 在新的应用程序域中创建对象
RemoteObject obj = (RemoteObject)newDomain.CreateInstanceAndUnwrap(
typeof(RemoteObject).Assembly.FullName,
typeof(RemoteObject).FullName);
// 调用方法
obj.SayHello();
// 卸载应用程序域
AppDomain.Unload(newDomain);
}
}
新域内切换上下文:
这本质上还是跨域传递,需要序列化特性才行.
using System;
using System.Runtime.Remoting.Messaging;
class Program {
static void Main() {
// 设置上下文数据
CallContext.LogicalSetData("MyKey", "Hello from Main Context!");
// 获取上下文数据
var data = CallContext.LogicalGetData("MyKey");
Console.WriteLine("Main Context Data: " + data);
// 创建一个新的上下文
using (new MyContext()) {
// 在新上下文中设置数据
CallContext.LogicalSetData("MyKey", "Hello from New Context!");
// 获取新上下文中的数据
var newData = CallContext.LogicalGetData("MyKey");
Console.WriteLine("New Context Data: " + newData);
}
// 回到主上下文后,检查数据
var originalData = CallContext.LogicalGetData("MyKey");
Console.WriteLine("Back to Main Context Data: " + originalData);
}
}
// 自定义上下文类
public class MyContext : IDisposable {
private readonly object _oldContext;
public MyContext() {
// 保存当前上下文
_oldContext = CallContext.LogicalGetData("MyKey");
// 设置新的上下文数据
CallContext.LogicalSetData("MyKey", "Initialized in New Context");
Console.WriteLine("New Context Created.");
}
public void Dispose() {
// 恢复原来的上下文数据
CallContext.LogicalSetData("MyKey", _oldContext);
Console.WriteLine("Context Restored.");
}
}
创建程序域两个函数是不同的
CreateInstance
CreateInstanceAndUnwrap
using System;
using System.IO;
using System.Reflection;
class Program {
static void Main() {
// 读取DLL到字节数组
byte[] dllBytes = File.ReadAllBytes("YourDll.dll"); // 替换为实际路径
// ----------- 主程序域加载 -----------
Console.WriteLine("【主程序域加载】");
Assembly mainAssembly = Assembly.Load(dllBytes); // 字节流加载[1](@ref)
Type mainType = mainAssembly.GetType("Namespace.ClassName"); // 替换为实际类名
dynamic mainInstance = Activator.CreateInstance(mainType);
mainInstance.YourMethod(); // 调用目标方法
// ----------- 新域加载 -----------
Console.WriteLine("\n【新域加载】");
AppDomain newDomain = AppDomain.CreateDomain("NewDomain");
try
{
// 使用Loader类实现跨域字节流加载
ObjectHandle loaderHandle = newDomain.CreateInstance(
typeof(CrossDomainLoader).Assembly.FullName,
typeof(CrossDomainLoader).FullName
);
CrossDomainLoader loader = (CrossDomainLoader)loaderHandle.Unwrap();
loader.LoadAndExecute(dllBytes, "Namespace.ClassName", "YourMethod");
}
finally
{
AppDomain.Unload(newDomain); // 卸载新域[2](@ref)
}
}
}
// 跨域加载辅助类(需继承MarshalByRefObject)
public class CrossDomainLoader : MarshalByRefObject
{
public void LoadAndExecute(byte[] dllBytes, string className, string methodName)
{
Assembly assembly = Assembly.Load(dllBytes); // 在新域中加载[3](@ref)
Type type = assembly.GetType(className);
dynamic instance = Activator.CreateInstance(type);
instance.GetType().GetMethod(methodName).Invoke(instance, null);
}
}
调试
卸载DLL
项目结构
- cad主插件工程,引用-->通讯类工程
- 通讯类工程(继承MarshalByRefObject接口的)
- 其他需要加载的子插件工程:cad子插件项目作为你测试加载的dll,
里面有一个cad命令gggg,它将会用来验证我们是否成功卸载.
和以往的工程都不一样的是,
我们需要复制一份acad.exe目录的所有文件到一个非C盘目录,
如下:
修改 主工程,属性页:
生成,输出: G:\AutoCAD 2008\ <--由于我的工程是.net standard,所以这里将会生成各类net文件夹
调试,可执行文件: G:\AutoCAD 2008\net35\acad.exe <--在net文件夹放acad.exe目录所有文件
为什么?因为通讯类.dll必须要在acad.exe旁边,否则无法通讯,
会报错,似乎是权限问题,至于有没有其他方法,我不知道.
通讯结构图
当时我搞卸载的时候怎么没看到下面链接,
新版net出来微软就弄了?拒绝内联之类的,我net3.5怎么阻止.
卸载的链接参考
acad卸载程序域是成功的,
但是存在COM的GCHandle问题:
在新域上面创建通讯类--卸载成功,不过关闭cad程序的时候弹出了报错:
System.ArgumentException:“无法跨 AppDomain 传递 GCHandle。”
我查询这个错误,大部分是讲COM的,而我工程上面刚好使用了COM,
所以最好就是检测是不是有静态字段持有COM,最好每次使用就执行释放,不要静态持有.
甚至怀疑是cad的Acap的文档集合本身就会创建COM导致异常...
你可以做做实验,实现一个没引用cad.dll的dll,
我用了winform,是可以卸载,且不会报GC错误.
旧工程:
Loadx分支呈现旧代码
最新工程:
https://www.cnblogs.com/JJBox/p/18800009
(完)
浙公网安备 33010602011771号