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

C# COM 互操作進階(二):介面查詢與 Marshal.QueryInterface、as/is、IUnknown 三種方式

     編輯:關於C#

COM 物件在 .NET 中由執行階段可呼叫包裝函數 (RCW) 包裝。查詢介面 (QueryInterface) 是 COM 核心機制,.NET 提供三種對應方式:Marshal.QueryInterface、C# 語言原生 as/is、以及自訂 IUnknown 介面。本文詳細比較三者用法、RCW 參考計數行為與最佳實踐。

RCW 類型身分差異

  • 類別介面 (Class Interface):Type.GetTypeFromCLSID / Activator.CreateInstance / CoCreateInstance 建立 → RCW 類型為匯入組件中定義的具體類別 (如 JetEngineClass)。
  • __ComObject:晚期繫結、Marshal.GetObjectForIUnknown、Marshal.GetActiveObject、Marshal.QueryInterface 回傳 → RCW 類型為 System.__ComObject。
  • 同一 COM 物件的不同介面指標,經 RCW 包裝後皆指向同一個 RCW 實例 (CLR 內部維護指標→RCW 映射表)。

方式一:Marshal.QueryInterface (底層手動控制)


IntPtr pUnk = GetComPointer(); // 取得 IUnknown 指標
Guid iid = typeof(IJetEngine).Guid;
IntPtr pJet;
int hr = Marshal.QueryInterface(pUnk, ref iid, out pJet);
if (hr < 0) Marshal.ThrowExceptionForHR(hr);

// 使用介面...
// 手動釋放
Marshal.Release(pJet);
  • 需自行管理參考計數:QueryInterface 成功會 AddRef,用畢必呼叫 Marshal.Release。
  • 適用於無法預知介面型別、動態查詢、或需精確控制生命週期的場景。

方式二:C# as / is 運算子 (推薦、語義自然)


object comObj = CreateComObject(); // RCW 包裝物件
IJetEngine jet = comObj as IJetEngine; // 內部呼叫 QueryInterface
if (jet != null) { /* 成功 */ }

bool hasInterface = comObj is IJetEngine; // 僅檢查,不取得介面
  • 語義與托管型別轉換一致:as 失敗回傳 null,is 回傳 bool,不拋出例外。
  • 自動參考計數:RCW 內部處理 AddRef/Release,開發者無需手動干預。
  • 不要求編譯期宣告介面:只要 COM 物件實作該介面,as 即可成功,即使 RCW 類別未顯式宣告實作該介面。
  • 避免冗余:if (obj is I) obj = obj as I; 屬二次查詢,直接 var i = obj as I; if (i != null) 即可。

方式三:自訂 IUnknown 介面 (進階、回傳指標)


[ComImport, Guid("00000000-0000-0000-C000-000000000046")]
interface IUnknown
{
    void QueryInterface(ref Guid iid, out IntPtr ppv);
    int AddRef();
    int Release();
}

[ComImport, CoClass(typeof(JetEngineClass)), Guid("...")]
interface IJetEngineEx : IUnknown { /* 方法... */ }

// 使用
IJetEngineEx jet = GetComObject() as IJetEngineEx;
IntPtr pUnk;
jet.QueryInterface(typeof(IUnknown).Guid, out pUnk);
  • 介面定義必須符合 COM v-table 順序:QueryInterface、AddRef、Release 在前三個位置。
  • 查詢結果為 IntPtr,後續仍需 Marshal.GetObjectForIUnknown 轉回 RCW 才方便使用,較繁瑣。

RCW 參考計數與生命週期管理

  • RCW 建立時對 COM 物件 AddRef (計數=1)。
  • RCW 之間的介面轉換 (as/QueryInterface) 不建立新 RCW,不增加 COM 計數。
  • RCW 為受控物件,受 GC 管理。當無變數參考 RCW 時,GC 回收並呼叫 Release,COM 計數歸零則銷毀。
  • 優先使用 RCW 參考 (object/介面),避免直接操作 IntPtr 導致計數洩漏或過早釋放。

參數傳遞技巧

  • 介面參數標記 [MarshalAs(UnmanagedType.Interface)]。
  • 不需完整定義的介面可用 object 或 IntPtr 佔位,配合 MarshalAs 屬性。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved