COM 物件在 .NET 中由執行階段可呼叫包裝函數 (RCW) 包裝。查詢介面 (QueryInterface) 是 COM 核心機制,.NET 提供三種對應方式:Marshal.QueryInterface、C# 語言原生 as/is、以及自訂 IUnknown 介面。本文詳細比較三者用法、RCW 參考計數行為與最佳實踐。
Type.GetTypeFromCLSID / Activator.CreateInstance / CoCreateInstance 建立 → RCW 類型為匯入組件中定義的具體類別 (如 JetEngineClass)。Marshal.GetObjectForIUnknown、Marshal.GetActiveObject、Marshal.QueryInterface 回傳 → RCW 類型為 System.__ComObject。
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。
object comObj = CreateComObject(); // RCW 包裝物件
IJetEngine jet = comObj as IJetEngine; // 內部呼叫 QueryInterface
if (jet != null) { /* 成功 */ }
bool hasInterface = comObj is IJetEngine; // 僅檢查,不取得介面
as 失敗回傳 null,is 回傳 bool,不拋出例外。AddRef/Release,開發者無需手動干預。as 即可成功,即使 RCW 類別未顯式宣告實作該介面。if (obj is I) obj = obj as I; 屬二次查詢,直接 var i = obj as I; if (i != null) 即可。
[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);
QueryInterface、AddRef、Release 在前三個位置。IntPtr,後續仍需 Marshal.GetObjectForIUnknown 轉回 RCW 才方便使用,較繁瑣。AddRef (計數=1)。as/QueryInterface) 不建立新 RCW,不增加 COM 計數。Release,COM 計數歸零則銷毀。IntPtr 導致計數洩漏或過早釋放。[MarshalAs(UnmanagedType.Interface)]。object 或 IntPtr 佔位,配合 MarshalAs 屬性。