2025-12-16

[C#] .NET 動態載入 DLL 可熱插拔:模組引入其他第三方 dll 的正確做法

接續上一篇 .NET 動態載入 DLL,可熱插拔 ,這邊做了一些測試,如果我寫了一個模組叫做

CSJson.dll 其中如果要引入 Newtonsoft.Json.dll ,那我要如何處理...

直接先說結論,不需要特殊處理,但是要記得 把 Newtonsoft.Json.dll 放入到主程式的 modules 檔案夾中即可。

重說一下案例

第一個  CShellCore ,這主要就是制定介面 ICShellModule、ICShellModuleContext、ICShellShell 全在這裡

第二個 CSJson ,可被抽換的模組,實作 ICShellModule 內部放一個 ToJson ,會自主呼叫 JSON.net

第三個 CSMain ,主要就是實作 動態載入 CSJson.dll,呼叫後立刻卸載


第一個 CShellCore 你在 這篇文章可以找到我就不贅述


 1. 我們這邊先製作 CSJson 的 module ,這邊我也不贅述,主要就是實作 ICShellShell, ICShellModuleContext 這邊我們就是透過 nuget 引入 JSON.net using CShellCore; namespace CSJson { public class JsonModule : ICShellModule { public Task OnLoad(ICShellModuleContext context) => Task.CompletedTask; public Task OnStart() => Task.CompletedTask; public Task OnStop() => Task.CompletedTask; // 回傳 string,不回傳 JObject 之類的型別 public string ToJson(object obj) { return Newtonsoft.Json.JsonConvert.SerializeObject(obj); } } } 這邊如果你編譯完在 bin 沒有找到 Newtonsoft.Json.dll ,記得修改 csproj 檔案加入 <PropertyGroup> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> </PropertyGroup> 2. CSMain 我們來設計一個 MyShellJson.cs ,這邊我在 LoadModules 的部分,我改成指定載入 CSJson.dll using CShellCore; using System.Reflection; namespace CSMain { public class MyShellJson : ICShellShell, ICShellModuleContext { private string? _sourcedllPath; public void LoadModules(string folderPath) { Directory.CreateDirectory(folderPath); //這裡我改成直接指定 _sourcedllPath = Directory.GetFiles(folderPath, "CSJson.dll").FirstOrDefault(); } public Task StartAsync() => Task.CompletedTask; public Task StopAsync() => Task.CompletedTask; public T? GetService<T>() => default; public string Execute(string input) { if (_sourcedllPath == null) return "(module not found)"; // 讀取 DLL(二進位方式)避免鎖檔 byte[] raw = File.ReadAllBytes(_sourcedllPath); var alc = new ModuleLoadContext(_sourcedllPath); using var ms = new MemoryStream(raw); var asm = alc.LoadFromStream(ms); // 找到符合 ICShellModule 的類型 var type = asm.GetTypes() .First(t => typeof(ICShellModule).IsAssignableFrom(t) && !t.IsInterface); var module = (ICShellModule)Activator.CreateInstance(type)!; module.OnLoad(this).Wait(); module.OnStart().Wait(); var data = new { Name = input, Age = new Random().Next(19,61) }; var result = type.InvokeMember( "ToJson", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, module, new object[] { data }); module.OnStop().Wait(); alc.Unload(); GC.Collect(); GC.WaitForPendingFinalizers(); return result?.ToString() ?? ""; } } } 主要程式 static async Task Main(string[] args) { Console.WriteLine("=== MSJSON Shell 啟動 ==="); var shell = new MyShellJson(); shell.LoadModules("modules"); await shell.StartAsync(); while (true) { Console.Write("輸入字串(exit 離開):"); var input = Console.ReadLine(); if (input == "exit") break; var output = shell.Execute(input ?? ""); Console.WriteLine($"Result:{output}\n"); GC.Collect(); GC.WaitForPendingFinalizers(); } await shell.StopAsync(); }

接下來,重點就是 你必須把步驟1 編譯出來的 CSJson.dll , Newtonsoft.Json.dll ,放入 bin/Debug/netX/modules/ 就可以動態載入執行了

這邊心得 AssemblyDependencyResolver 比我想的還要聰明

真正困難的不是 程式碼,而是觀念,一旦搞懂 只載主模組、相依交給 resolver,很多原本看起來複雜的問題會自然消失

雖然這種架構不一定適合所有專案,但在需要模組化更新或不想因為小功能就重啟主程式時,確實是一個值得嘗試的方向

--

The bug existed in all possible states. Until I ran the code.

如果這篇文章有幫助到您幫我分享一下,讓我有寫下去的動力...