cad.net dll動態加載和卸載
20250320
不應該loadx時候采用鏈式加載,而是loadx命令只加載目標dll,
每個加載的dll應該獨立程序域.
如果加入了同名不同版本的,就可以自動卸載之前版本的.
當觸發需要之前的版本時候,通過事件加載回來.
我實現在此處:
插件式架構最小實現:
http://www.rzrgm.cn/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時候出現斷點
// http://www.rzrgm.cn/DasonKwok/p/10510218.html
// http://www.rzrgm.cn/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分支呈現舊代碼
最新工程:
http://www.rzrgm.cn/JJBox/p/18800009
(完)
浙公網安備 33010602011771號