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

深入解析 System.AddIn:打造可擴展的 Windows Forms 應用程式

     編輯:關於.NET

前言:為什麼需要加載項架構?

現代應用程式開發越來越重視「可擴展性」。使用者希望能在不修改核心程式碼的情況下,動態新增功能。Microsoft .NET Framework 3.5 引入的 System.AddIn(代號「增益集框架」)正是為了解決這個問題而生。它提供了一套完整的管線機制,支援版本控制、隔離層級(進程內、應用程式域、進程外)以及安全權限管理,讓宿主應用程式能安全地載入並執行第三方擴充元件。

案例研究:ShapeApp 繪圖應用程式

本文以 ShapeApp 為例,示範如何將現有的 Windows Forms 應用程式改造為支援加載項的宿主。ShapeApp 是一個簡單的繪圖工具,支援基本圖形(正方形、圓形等)的新增、移動、顏色與大小變更,並可管理多個繪圖分頁。原始版本由 VSTA 團隊開發,作為嵌入 Visual Studio IDE 的範例;我們將從其非擴充版本著手,逐步整合 System.AddIn。

ShapeApp 畫面截圖
圖 1 ShapeApp 的畫面截圖

第一步:調整宿主,定義公開的物件模型

加載項框架的隔離機制要求:宿主視圖組件不得參考除 .NET Framework 以外的任何組件,且禁止使用 MarshalByRefObject。因此,我們必須先梳理出加載項真正需要使用的類型,定義乾淨的介面契約。

類型說明
ShapeApplication主應用程式物件,提供應用程式層級操作
Drawing代表單一文件/繪圖
Shape代表繪圖中的單一圖形
DrawingCollection / ShapeCollection集合類型
EventArgs 衍生類型自訂事件參數
圖 2 ShapeApp 核心類型清單

第二步:建立加載項管理器

宿主需要一種機制來發現、載入與管理加載項。範例中實作了一個簡單的 AddInManager 靜態類別與管理視窗(透過選單項目叫用),核心邏輯僅需三行程式碼:

// 更新加載項存儲(掃描應用程式目錄)
string[] warnings = AddInStore.Update(PipelineStoreLocation.ApplicationBase);

// 取得宿主視圖介面類型
Type hostViewOfAddIn = typeof(ShapeAppHostViews.IShapeAddIn);

// 尋找所有實作該視圖的加載項
ICollection<AddInToken> addIns = AddInStore.FindAddIns(hostViewOfAddIn, PipelineStoreLocation.ApplicationBase);

啟用加載項同樣簡潔:

IShapeAddIn addIn = token.Activate<IShapeAddIn>(AddInSecurityLevel.FullTrust);

框架支援多種隔離層級(進程內、新應用程式域、新進程)與信任等級,開發者可依安全需求彈性選擇。

第三步:公開物件模型 —— 宿主視圖與 EIMI 技巧

在 HostViews 組件中,以 介面 定義宿主視圖(而非抽象類別),主要原因有三:

  1. Visual Basic 事件綁定需介面支援;
  2. C# 不支援多重繼承,介面可讓既有類別直接實作;
  3. 支援 EIMI(顯式介面成員實作),這是解決型別不匹配的關鍵。
namespace ShapeAppHostViews {
    public interface IShapeApplication {
        IDrawing ActiveDrawing { get; }
        IShapeCollection AvailableShapes { get; }
        IDrawingCollection Drawings { get; }
        bool Visible { get; set; }
        IDrawing NewDrawing();
        void Quit();
        event EventHandler<CreatedDrawingEventArgs> CreatedDrawing;
    }
}
圖 4 IShapeApplication 宿主視圖介面

宿主類別 ShapeApplication 需實作此介面。對於簽名相同的成員(如 Visible),直接滿足介面契約即可;但對於回傳內部具象類型的成員(如 ActiveDrawing 回傳 Drawing),必須使用 EIMI 進行轉接:

// 原始屬性,回傳內部具象類型
public Drawing ActiveDrawing { get { ... } }

// EIMI 實作介面成員,回傳視圖介面
ShapeAppHostViews.IDrawing ShapeAppHostViews.IShapeApplication.ActiveDrawing {
    get { return this.ActiveDrawing; } // 隱式轉換
}

執行緒安全:跨執行緒存取 UI 控制項

加載項可能從任何執行緒呼叫公開成員,若直接操作 Windows Forms 控制項會導致不可預期行為。必須在每個公開成員中檢查 Control.InvokeRequired 並透過 Control.Invoke 執行:

private delegate Drawing NewDrawingDelegate();
public Drawing NewDrawing() {
    if (this.ApplicationForm.InvokeRequired) {
        var del = new NewDrawingDelegate(NewDrawing);
        return (Drawing)ApplicationForm.Invoke(del);
    }
    // 原始邏輯...
}

第四步:建立管線 —— 適配器、約定與加載項視圖

管線組件包括:宿主視圖 → 宿主端適配器 → 約定 → 加載項端適配器 → 加載項視圖。可使用 Pipeline Builder 工具自動產生,或手工編寫以支援跨版本相容。

物件類型與適配器方向

物件類型所在位置所需適配器
宿主端物件(如 ShapeApplication) 宿主 「視圖→約定」宿主適配器 + 「約定→視圖」加載項適配器
加載項端物件(如 ShapeAddIn) 加載項 方向相反,適配器位置互換
兩端物件 雙方皆可建立 每端各兩個適配器(共四個),若視圖共用組件可減為兩個

約定中模擬事件

約定不支援原生事件,需以 Add/Remove 模式模擬:

// 約定介面
public interface IShapeApplicationContract : IContract {
    void CreatedDrawingAdd(ICreatedDrawingEventHandlerContract handler);
    void CreatedDrawingRemove(ICreatedDrawingEventHandlerContract handler);
}
public interface ICreatedDrawingEventHandlerContract : IContract {
    void Handler(ICreatedDrawingEventArgsContract args);
}

適配器負責在兩側轉譯事件訂閱與觸發。注意:加載項必須在同一適配器實例上註冊與解除註冊事件,否則解除無效。

物件身分識別問題

同一宿主物件多次傳回加載項時,會產生多個適配器實例,導致 ReferenceEquals 失敗、集合 Contains 判斷錯誤。解決方案:

  • 在適配器中覆寫 Equals 與 GetHashCode,轉呼叫真實宿主物件;
  • 或實作適配器快取(需弱參考機制,避免記憶體洩漏)。

第五步:撰寫加載項 —— 以協作加載項為例

加載項開發極為簡單:建立實作加載項視圖的類別,標記 [AddIn] 屬性即可。範例中包含一個 協作加載項,展示強大功能:

  • 透過 WCF 連線兩台電腦的 ShapeApp 實例;
  • 即時同步繪圖操作(新增圖形、移動、屬性變更);
  • 支援多方連線(圖 10 展示三台電腦同步編輯)。
協作加載項 UI
圖 8 協作加載項使用者介面
事件傳播路徑
圖 9 事件透過協作加載項傳播的路徑
三台電腦連線
圖 10 三個 ShapeApp 實例相互連線協作

協作加載項訂閱宿主所有事件,將使用者操作封裝為訊息透過 WCF 轉發;接收端在重播操作時暫時取消事件訂閱,避免無限迴圈。

加載項 UI 的三種呈現方式

  1. 顯示獨立視窗(部分信任模式下會顯示安全性警告);
  2. 直接在宿主視窗承載 WPF 控制項;
  3. 將 Windows Forms 控制項包裝在 WPF 容器中(WindowsFormsHost)。

總結與資源

System.AddIn 提供了企業級的擴充性基礎設施,雖然管線建置初期投入較大,但換來的是嚴格的版本隔離、安全邊界與長期維護性。ShapeApp 案例證明:只需公開核心物件模型,即可衍生出「自動化加載項」與「協作編輯」等高階應用。

延伸閱讀:CLR 加載項團隊部落格資源頁、Pipeline Builder 工具、團隊部落格回饋管道

作者:Mueez Siddiqui,Microsoft CLR 安全性與擴充性團隊軟體開發工程師。

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