聊一聊 .NET 的 AssemblyLoadContext 可插拔程序集

一:背景

1. 講故事

最近在分析一個崩潰dump時,發現禍首和AssemblyLoadContext有關,說實話這東西我也比較陌生,后來查了下大模型,它主要奔著替代 .NetFrameWork 時代的 AppDomain 的,都是用來做晚期加卸載,實現對宿主程序的可插拔,AppDomain.Create 是在AppDomain級別上,后者是在 Assembly 級別上。

二:Assembly 插拔分析

1. 一個簡單的案例

簡單來說這東西可以實現 Assembly 的可插拔,這個小案例有三個基本元素。

  1. IPlugin 組件接口

這塊比較簡單,新建一個類庫,里面主要就是組件需要實現的接口。


namespace MyClassLibrary.Interfaces
{public interface IPlugin{string Name { get; }string Version { get; }void Execute();string GetResult();}
}
  1. SamplePlugin 組件實現

新建一個組件,完成這些接口方法的實現。

public class SamplePlugin : IPlugin{public string Name => "Sample Plugin";public string Version => "1.0.0";public void Execute(){Console.WriteLine("SamplePlugin is executing...");}public string GetResult(){return "Hello from SamplePlugin!";}}
  1. 自定義的 CustomAssemblyLoadContext 上下文

最后就是在調用處自定義下 AssemblyLoadContext 以及簡單調用,參考代碼如下:


namespace Example_1_6
{internal class Program{static void Main(string[] args){Console.WriteLine("=== 插件系統啟動 ===");// 設置插件目錄string pluginsPath = @"D:\sources\woodpecker\Test\MyClassLibrary\bin\Debug\net8.0\";Console.WriteLine($"插件路徑: {pluginsPath}");var dllFile = Directory.GetFiles(pluginsPath, "MyClassLibrary.dll").FirstOrDefault();var _loadContext = new CustomAssemblyLoadContext("MyPluginContext", pluginsPath);var assembly = _loadContext.LoadAssembly(dllFile);var type = assembly.GetType("MyClassLibrary.SamplePlugin");IPlugin plugin = (IPlugin)Activator.CreateInstance(type);Console.WriteLine($"- {plugin.Name} v{plugin.Version}");Console.WriteLine($"\n執行插件: {plugin.Name} v{plugin.Version}");plugin.Execute();string result = plugin.GetResult();Console.WriteLine($"插件返回: {result}");Console.ReadKey();}}public class CustomAssemblyLoadContext : AssemblyLoadContext{private readonly string _dependenciesPath;public CustomAssemblyLoadContext(string name, string dependenciesPath): base(name, isCollectible: true){_dependenciesPath = dependenciesPath;}public Assembly LoadAssembly(string assemblyPath){return LoadFromAssemblyPath(assemblyPath);}public new void Unload(){base.Unload();}}
}

將代碼運行起來,可以看到插件代碼得到執行。

2. 組件已經插上了嗎

plugin中的方法都已經執行了,那 MyClassLibrary.dll 自然就插上去了,接下來如何驗證呢?可以使用 windbg 的 !dumpdomain 命令即可。


0:015> !dumpdomain
--------------------------------------
System Domain:      00007ff8e9d4b150
LowFrequencyHeap:   00007FF8E9D4B628
HighFrequencyHeap:  00007FF8E9D4B6B8
StubHeap:           00007FF8E9D4B748
Stage:              OPEN
Name:               None
--------------------------------------
Domain 1:           00000211d617dc80
LowFrequencyHeap:   00007FF8E9D4B628
HighFrequencyHeap:  00007FF8E9D4B6B8
StubHeap:           00007FF8E9D4B748
Stage:              OPEN
Name:               clrhost
Assembly:           00000211d613e560 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.16\System.Private.CoreLib.dll]
ClassLoader:        00000211D613E5F0Module00007ff889d54000    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.16\System.Private.CoreLib.dll...Assembly:           000002118052b0d0 [D:\sources\woodpecker\Test\MyClassLibrary\bin\Debug\net8.0\MyClassLibrary.dll]
ClassLoader:        000002118052B160Module00007ff88a11c060    D:\sources\woodpecker\Test\MyClassLibrary\bin\Debug\net8.0\MyClassLibrary.dll

從卦中可以清晰的看到 MyClassLibrary.dll 已經成功的送入。

3. 組件如何卸載掉

能不能卸載掉,其實取決于你在 new AssemblyLoadContext() 時塞入的 isCollectible 字段決定的,如果為true就是一個可卸載的程序集,參考代碼如下:

public CustomAssemblyLoadContext(string name, string dependenciesPath): base(name, isCollectible: true){_dependenciesPath = dependenciesPath;}

其次要知道的是卸載程序集是一個異步操作,不要以為調用了 UnLoad() 就會立即卸載,它只是起到了一個標記刪除的作用,只有程序集中的實例無引用根了,即垃圾對象的時候,再后續由 GC 來實現卸載。

這一塊我們可以寫段代碼來驗證下,我故意將邏輯包裝到 DoWork() 方法中,然后處理完之后再次觸發GC,修改后的代碼如下:

internal class Program{static void Main(string[] args){DoWork();GC.Collect();GC.WaitForPendingFinalizers();Console.WriteLine("GC已觸發,請再次觀察 Assembly 是否被卸載...");Console.ReadLine();}static void DoWork(){Console.WriteLine("=== 插件系統啟動 ===");// 設置插件目錄string pluginsPath = @"D:\sources\woodpecker\Test\MyClassLibrary\bin\Debug\net8.0\";Console.WriteLine($"插件路徑: {pluginsPath}");var dllFile = Directory.GetFiles(pluginsPath, "MyClassLibrary.dll").FirstOrDefault();var _loadContext = new CustomAssemblyLoadContext("MyPluginContext", pluginsPath);var assembly = _loadContext.LoadAssembly(dllFile);var type = assembly.GetType("MyClassLibrary.SamplePlugin");IPlugin plugin = (IPlugin)Activator.CreateInstance(type);Console.WriteLine($"- {plugin.Name} v{plugin.Version}");Console.WriteLine($"\n執行插件: {plugin.Name} v{plugin.Version}");plugin.Execute();string result = plugin.GetResult();Console.WriteLine($"插件返回: {result}");_loadContext.Unload();Console.WriteLine("程序集已標記為卸載... 請觀察 Assembly 是否被卸載...");Console.ReadKey();}}

從卦中可以看到確實已經不再有 MyClassLibrary.dll 程序集了,但托管堆上還有 CustomAssemblyLoadContext 死對象,當后續GC觸發時再回收,用windbg驗證如下:


0:014> !dumpobj /d 238e9c464c8
Name:        Example_1_6.CustomAssemblyLoadContext
MethodTable: 00007ff88a06f098
EEClass:     00007ff88a079008
Tracked Type: false
Size:        88(0x58) bytes
File:        D:\sources\woodpecker\Test\Example_1_6\bin\Debug\net8.0\Example_1_6.dll
Fields:MT    Field   Offset                 Type VT     Attr            Value Name
00007ff889e870a0  4001116       30        System.IntPtr  1 instance 000002388042A8F0 _nativeAssemblyLoadContext
00007ff889dd5fa8  4001117        8        System.Object  0 instance 00000238e9c46520 _unloadLock
0000000000000000  4001118       10                       0 instance 0000000000000000 _resolvingUnmanagedDll
0000000000000000  4001119       18                       0 instance 0000000000000000 _resolving
0000000000000000  400111a       20                       0 instance 0000000000000000 _unloading
00007ff889e8ec08  400111b       28        System.String  0 instance 0000023880006a30 _name
00007ff889e3a5f0  400111c       38         System.Int64  1 instance                0 _id
00007ff889f2f108  400111d       40         System.Int32  1 instance                1 _state
00007ff889ddd070  400111e       44       System.Boolean  1 instance                1 _isCollectible
00007ff88a0ed120  4001114      a00 ...Private.CoreLib]]  0   static 00000238e9c46550 s_allContexts
00007ff889e3a5f0  4001115      bc0         System.Int64  1   static                1 s_nextId
0000000000000000  400111f      a08 ...yLoadEventHandler  0   static 0000000000000000 AssemblyLoad
0000000000000000  4001120      a10 ...solveEventHandler  0   static 0000000000000000 TypeResolve
0000000000000000  4001121      a18 ...solveEventHandler  0   static 0000000000000000 ResourceResolve
0000000000000000  4001122      a20 ...solveEventHandler  0   static 0000000000000000 AssemblyResolve
0000000000000000  4001123      a28                       0   static 0000000000000000 s_asyncLocalCurrent
00007ff889e8ec08  4000001       48        System.String  0 instance 0000023880006938 _dependenciesPath0:014> !gcroot 238e9c464c8
Caching GC roots, this may take a while.
Subsequent runs of this command will be faster.Found 0 unique roots.

三:總結

有時候感嘆 知識無涯人有涯,在 dump分析中不斷的螺旋式提升,理論指導實踐,實踐反哺理論。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/94817.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/94817.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/94817.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java中使用Spring Boot+Ollama實現本地AI的MCP接入

目錄結構完善spring bootpom.xml添加依賴application.ymlMCP 工具配置 mcp-servers.json配置類編寫API在我的上一篇文章搭建好本地的聊天機器人后,準備接入MCP進一步增強AI的能力,以實現類似手機AI的功能 參考的是第二篇文章鏈接其內容比較精煉&#x…

C#正則表達式與用法

🌟 C# 常用正則表達式與用法C# 使用正則需要引用命名空間:using System.Text.RegularExpressions; 常用方法:Regex.IsMatch(input, pattern) → 返回 bool,用于驗證Regex.Match(input, pattern) → 返回 Match 對象,可…

從0開始學習Java+AI知識點總結-27.web實戰(Maven高級)

一、分模塊設計與開發:讓項目結構更清晰1.1 為什么需要分模塊?單模塊開發的痛點在小型項目中,單模塊(所有代碼放在一個工程)或許能滿足需求,但項目規模擴大后會出現兩大核心問題:維護成本高&…

Ferris Wheel (貪心 | 雙指針)

題目:思路:本題注意題目的條件即可,題意說一個摩天輪可以坐一個人或者兩個人,那么顯然我們就可以貪心一下具體的,我們可以讓最小的去匹配最大的,如果此時大于 x,那么顯然我們根本無法使得 最大的…

課程視頻怎么加密?在線教育機構常用的6個課程加密方法

知識付費時代,課程視頻是教育機構的核心資產。但是不難發現,課程視頻的安全卻得不到保障。各大購物平臺搜索課程名稱,便出現了許多盜版課程。如何有效防止課程被翻錄和二次傳播,成為急需解決的關鍵問題。今天這期分享點干貨&#…

SOME/IP-SD中”服務器服務組播端點”、“客戶端服務組播端點”與“IPv4組播選項的區分

<摘要> AUTOSIP-SD協議中組播端點&#xff08;Multicast Endpoint&#xff09;在不同上下文中的角色與表述差異。準確理解“服務器服務組播端點”、“客戶端服務組播端點”與“IPv4組播選項”中配置的端點之間的關系&#xff0c;是正確實現組播事件分發機制的關鍵。這涉及…

計算機是如何運行的

目錄 一&#xff0c;計算機是如何組成的 1.1&#xff0c;CPU中央處理單元 1.1.1&#xff0c;CPU的構成和屬性 1.1.2&#xff0c;如何判斷cpu的好壞 1.1.3&#xff0c;指令 1.1.4&#xff0c;CPU的緩存 1.2&#xff0c;操作系統 1.2.1&#xff0c;進程 1.2.2&#xff0…

JavaScript性能優化:實戰技巧與高效策略

JavaScript性能優化實戰技術文章大綱性能優化的重要性解釋為什么性能優化對用戶體驗和業務指標至關重要列舉常見性能問題的影響&#xff08;如跳出率、轉化率下降&#xff09;代碼層面的優化減少全局變量使用&#xff0c;避免內存泄漏使用事件委托減少事件監聽器的數量避免頻繁…

解決.env.production 寫死 IP 的問題:Vue + config.json 運行時加載方案

背景&#xff1a;前端常用 .env.production 在構建時寫死 API 地址 場景&#xff1a;運維部署時經常不知道目標主機 IP/域名 問題&#xff1a;每次 IP 變動都要重新編譯 → 增加運維成本 引出需求&#xff1a;只修改 IP 就能完成部署&#xff0c;不需要重新打包 目錄一、解決方…

如何從三星手機轉移到另一部三星手機

三星Galaxy S系列因其出色的設計、令人驚嘆的顯示屏、驚艷的攝像頭、更好的揚聲器以及創新的指紋傳感器而受到大多數用戶的歡迎&#xff0c;獲得了良好的聲譽。讓用戶感到滿意的是&#xff0c;三星Galaxy S10擁有更美觀的設計、令人驚嘆的顯示屏、令人驚嘆的攝像頭、更好的揚聲…

聚焦建筑能源革新!安科瑞 “光儲直柔” 方案護航碳中和目標實現

1、背景在 “雙碳” 目標引領下&#xff0c;能源結構轉型與建筑能效提升成為重要課題。清華大學江億院士提出的 “光儲直柔” 新型配電系統&#xff0c;為建筑領域綠色發展提供了創新方向。光儲直柔得到了業界廣泛認同和積極響應&#xff0c;國家、各部委、地區陸續出臺相關政策…

Shell 中 ()、(())、[]、{} 的用法詳解

文章目錄Shell 中 ()、(())、[]、{} 的用法詳解一、先明確&#xff1a;四類符號的核心功能定位二、逐個拆解&#xff1a;用法、示例與避坑點1. ()&#xff1a;子 Shell 執行&#xff0c;隔離環境核心用法1&#xff1a;子 Shell 執行命令&#xff0c;隔離變量核心用法2&#xff…

開發避坑指南(41):Vue3 提示框proxy.$modal.msgSuccess()提示文本換行解決方案

需求 由于接口返回的提示信息過長&#xff0c;接口已經在返回提示中加入換行標簽了&#xff0c;但是使用proxy.modal.msgSuccess(res.msg)提示沒有換行&#xff0c;那么Vue3中proxy.modal.msgSuccess(res.msg)提示沒有換行&#xff0c;那么Vue3 中 proxy.modal.msgSuccess(res.…

[Sync_ai_vid] 唇形同步推理流程 | Whisper架構

鏈接&#xff1a;https://github.com/bytedance/LatentSync/blob/main/docs/syncnet_arch.md docs&#xff1a;LatentSync LatentSync是一個端到端唇語同步項目&#xff0c;能夠生成語音與唇形完美匹配的逼真視頻。 該項目通過使用*音頻條件化3D U-Net*&#xff08;一種生成式…

uniapp中 ios端 scroll-view 組件內部子元素z-index失效問題

發現子組件中的彈窗在ios手機上會被限制在scroll-view里面&#xff0c;安卓手機上不受限制&#xff0c;網上找了好久原因 scroll-view組件內部設置了 -webkit-overflow-scrolling: touch 樣式&#xff0c;導致z-index失效&#xff08;safari 3D變換會忽略z-index的層級問題&…

PyTorch圖像預處理完全指南:從基礎操作到GPU加速實戰

引言 圖像預處理是模型性能的"隱形基石"&#xff0c;在計算機視覺任務中直接決定模型能否提取有效特征。科學的預處理流程能讓基礎模型性能提升15%以上&#xff0c;而GPU加速預處理可使數據準備階段耗時降低60%以上。本文將聚焦PyTorch預處理核心技術&#xff0c;從基…

【前端教程】 CSS浮動布局解析與優化:從基礎實現到工程化改進

浮動(float)是CSS中實現頁面布局的經典技術,雖然現代布局更多使用Flexbox和Grid,但理解浮動的工作原理仍是前端開發者的基礎素養。本文以一個三欄浮動布局的代碼為例,從布局原理解析、潛在問題診斷、工程化優化三個維度,帶你深入理解浮動布局的精髓與優化思路。 一、原代…

DVWA靶場通關筆記-暴力破解(Impossible級別)

目錄 一、查看源碼 二、功能分析 三、SQL注入分析 1、使用PDO預處理語句和參數綁定 2、mysqli_real_escape_string轉義 3、stripslashes去除反斜杠 四、暴力破解分析 1、token防止暴力破解機制 2、登錄失敗隨機延遲機制 3、登陸失敗報錯信息相同 4、登陸失敗的賬戶…

IAR工程如何生成compile_commands.json文件(能生成但是clangd不能生成“.cache文件”)

最近一直在使用vscodeclangd的方式編寫代碼&#xff0c;感覺使用clangd查找函數調用、函數聲明、類型定義等等都比使用vscode自帶的c/c插件好用太多了。現在我有一個功能是IAR版本的&#xff0c;那么有沒有辦法生成clangd使用的compile_commands.json文件呢&#xff1f;答案是&…

QT5.14.2、CMake 擴展openCV

一、準備工具Qt5.14.2c11cmake3.24.0opencv3.4.16二、使用cmake可擴展opencv 首先解壓cmake、opencv 兩個下載的壓縮包&#xff0c;如下&#xff1a;運行cmake-gui.exe打開后有彈窗選擇&#xff0c;然后進入QT的安裝路徑下找 mingw73_64文件下的 C和C的執行文件這個截圖是我擴展…