首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WPF 插件架構:跨進程承載不受信任第三方 .NET 組件的最佳實踐

     編輯:關於.NET

Bloomberg 應用程式入口網站承載數十萬用戶使用的第三方 WPF 應用程式,其核心架構採用 獨立進程插件宿主 + AppDomain 隔離 + .NET Remoting 進程間通訊。本文解析此架構關鍵設計決策,並提供可直接參考的原始碼實作(含示範插件 SAPP/DEMO)。

架構總覽

插件宿主架構圖

  • Main.exe:主應用程式進程,負責複合 UI 組合、提供應用服務。
  • PluginHost.exe:獨立插件宿主進程,隔離第三方程式碼對主程式的影響。
  • Hosting.dll:共用組件,含 PluginController (預設 AppDomain) 與 PluginContainer (插件專屬 AppDomain)。
  • PAC (Private Assembly Cache):應用程式專屬組件快取,類似 GCP 但隔離於系統 GAC。
  • Plugins\{Mnemonic}\:每個插件獨立資料夾,含 Metadata.xml 及組件。

為何跨進程?優缺點權衡

優點缺點
主進程效能/穩定性不受插件拖累需自行實作 IPC 機制與版本控管
降低 UI 執行緒被插件阻塞風險需管理宿主進程生命週期
記憶體洩漏、未處理例外不波及主進程安全性架構複雜度提升
32 位進程獲得更大虛擬記憶體空間
可擴充非 .NET 主應用程式

AppDomain 隔離:.NET 內建沙盒

每個插件載入獨立 AppDomain,具備:

  • 型別安全隔離:跨域無法直接存取物件,須透過 MarshalByRefObject 或序列化。
  • 獨立設定檔:可針對不同插件調整載入行為。
  • 可卸載:AppDomain.Unload 釋放插件所有組件(除非以 LoaderOptimization.MultiDomain 載入)。

目錄結構與部署


AppRoot/
├── Main.exe
├── PluginHost.exe
├── Hosting.dll
├── PAC/
│   ├── Bloomberglp.Blpapi.dll
│   └── WPFToolkit.dll
└── Plugins/
    ├── SAPP/
    │   ├── Metadata.xml
    │   └── SimpleApp.dll
    └── DEMO/
        ├── Metadata.xml
        └── DapiSample.dll

Metadata.xml 定義進入點:

<Plugin>
  <Title>Simple App</Title>
  <MainAssembly>SimpleApp</MainAssembly>
  <MainClass>SimpleApp.Main</MainClass>
</Plugin>

啟動流程:從 Remoting 到插件 UI 執行緒

  1. PluginHost.exe 啟動,預設 AppDomain 建立 PluginController (繼承 MarshalByRefObject,覆寫 InitializeLifetimeService 回傳 null 防止 5 分鐘回收)。
  2. Main.exe 透過 .NET Remoting 呼叫 PluginController.Launch("DEMO IBM Equity")。
  3. PluginController 依助碼鍵 (Mnemonic) 組合插件路徑,建立新 AppDomainSetup:

var appPath = Path.Combine(_appsRoot, mnemonic);
var setup = new AppDomainSetup { ApplicationBase = appPath };
// 動態產生組件解析設定,指定 Hosting.dll 與 PAC 組件 codeBase
setup.SetConfigurationBytes(Encoding.UTF8.GetBytes(GenerateConfigXml()));
var domain = AppDomain.CreateDomain(mnemonic, null, setup);
  1. 使用 CreateInstanceAndUnwrap (避免 load-from 上下文) 在新域建立 PluginContainer 並呼叫 Launch(args)。
  2. PluginContainer 建立 STA 執行緒 (ApartmentState.STA) 啟動 WPF Application.Run(window) 訊息迴圈。
  3. 透過反射載入 MainAssembly、尋找 MainClass.CreateRootElement(string[]) 靜態方法,取回 UIElement 包裝為 Window 顯示。
  4. 視窗關閉後 AppDomain.Unload 清理資源。

關鍵技術細節

避免 load-from 上下文

直接用 CreateInstanceFromAndUnwrap 會導致組件載入到 load-from 上下文,造成原生映像無法使用、啟動變慢。正確做法:CreateInstanceAndUnwrap + <codeBase> 設定。

動態產生 AppDomain 設定 XML


<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="PluginHost.Hosting" publicKeyToken="..." culture="neutral"/>
        <codeBase version="1.0.0.0" href="Hosting.dll"/>
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Bloomberglp.Blpapi" publicKeyToken="..." culture="neutral"/>
        <codeBase version="3.6.1.0" href="PAC/Bloomberglp.Blpapi.dll"/>
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

示範插件 DEMO:Bloomberg API + WPF Chart

  • 使用 Bloomberglp.Blpapi 連接 DAPI,請求 HistoricalDataRequest (PX_LAST 欄位)。
  • 非同步處理回應事件,建構 DataPoint 序列。
  • WPF Toolkit Chart/LineSeries 繪製價格走勢圖 (MS-PL 授權,支援部分信任環境)。

DEMO 插件歷史價格圖表

安全性考量

本文未涵蓋完整沙箱實作 (如 Chromium 沙箱、Code Access Security、AppDomain 權限集)。生產環境需額外實作:

  • 限制檔案/網路/登錄存取權限。
  • 防止插件竊取主程式記憶體或注入程式碼。
  • 數位簽章驗證插件來源。

結語

「獨立進程 + 多 AppDomain + Remoting」是承載不受信任 WPF 插件的成熟模式,兼顧隔離性、擴充性與開發體驗。隨著 .NET Core/.NET 5+ 推出 AssemblyLoadContext 取代 AppDomain,跨平台插件架構正在演進,但核心設計哲學——**最小權限、失敗隔離、版本獨立**——始終不變。

下載完整原始碼 (archive.msdn.microsoft.com/mag201308Plugins) 即刻啟動你的插件生態系統建設。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved