接續上一篇 .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.
如果這篇文章有幫助到您幫我分享一下,讓我有寫下去的動力...