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

用 C 語言實作 COM 介面 IFoo:虛函數表、IUnknown 與指標運算深度解析

     編輯:關於C語言

專案結構

  • ifoo.h:介面定義(IFoo, IFooVtbl, IID_IFoo)。
  • outside.c:COutside 結構體實作 IFoo。
  • util.h:巨集、全域函數宣告。
  • main.c:main()、記憶體管理 Alloc/Free、IID 定義。

COM 介面本質:虛函數表指標

typedef struct IFoo {
    struct IFooVtbl *lpVtbl;
} IFoo;

typedef struct IFooVtbl IFooVtbl;
struct IFooVtbl {
    HRESULT (__stdcall *QueryInterface)(IFoo *This, const IID *const, void **);
    ULONG   (__stdcall *AddRef)(IFoo *This);
    ULONG   (__stdcall *Release)(IFoo *This);
    HRESULT (__stdcall *SetValue)(IFoo *This, int);
    HRESULT (__stdcall *GetValue)(IFoo *This, int *);
};

IFoo 實例記憶體佈局:首欄位指向唯讀虛函數表 IFooVtbl。C++ 編譯器隱含 this 指標;C 語言需显式在每個函數首參數傳遞 This。

IUnknown 核心契約

  1. 前三函數必為 QueryInterface, AddRef, Release(繼承 IUnknown)。
  2. QueryInterface(IID_IUnknown) 回傳指標必須恆定,作為物件身分比較依據。
  3. 參考計數:AddRef 增、Release 減,歸零時銷毀物件。

巨集封裝與強制轉型機制

#define IUNK_VTABLE_OF(x) ((IUnknownVtbl *)((x)->lpVtbl))
#define QueryInterface(pif, iid, ppv) \
    (IUNK_VTABLE_OF(pif)->QueryInterface((IUnknown*)(pif), iid, (void**)(ppv)))
#define AddRef(pif) (IUNK_VTABLE_OF(pif)->AddRef((IUnknown*)(pif)))
#define Release(pif) (IUNK_VTABLE_OF(pif)->Release((IUnknown*)(pif)))

為何要轉型為 IUnknownVtbl*?
雖然 IFooVtbl 前三成員與 IUnknownVtbl 記憶體佈局相同,但 C 型別系統要求指標型別匹配。巨集執行:
1. 取 lpVtbl(IFooVtbl*)。
2. 強轉為 IUnknownVtbl*(編譯器僅修改型別資訊,位址值不變)。
3. 呼叫對應函數,首參數同樣強轉為 IUnknown*。
實際執行時仍呼叫 COutside 實作的 FooQueryInterface 等函數,參數值(物件位址)完全一致。

物件實作:從介面指標反推實例指標

typedef struct COutside {
    IFoo ifoo;        // 嵌入介面,必須為第一個成員
    int refCount;
    int value;
} COutside;

// QueryInterface 實作
HRESULT __stdcall FooQueryInterface(IFoo *This, const IID *riid, void **ppv) {
    COutside *this = (COutside *)((char *)This - offsetof(COutside, ifoo));
    // 或利用 (size_t)&(((COutside*)0)->ifoo) 計算偏移
    if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IFoo)) {
        *ppv = &this->ifoo;
        this->refCount++;
        return S_OK;
    }
    *ppv = NULL;
    return E_NOINTERFACE;
}

關鍵技巧:offsetof 或 (size_t)&(((COutside*)0)->ifoo) 編譯期計算成員偏移,利用「位址 0 合法取偏移」特性(不解參考不觸發 GP),從介面指標反推物件基址。

編譯設定要點

  • 呼叫慣例:/Gz(預設 __stdcall)統一介面函數慣例。
  • 編譯模式:/TC(強制 C 模式),避免 C++ 名稱修飾干擾。
  • 預處理輸出 .i 檔可驗證巨集展開與虛函數表結構。

函數指標語法回顧

HRESULT (__stdcall *pQI)(IFoo*, const IID*, void**);  // 變數宣告
typedef HRESULT (__stdcall *QIType)(IFoo*, const IID*, void**); // 型別別名
pQI = QueryInterface;  // 賦值
pQI(this, &iid, &ptr); // 呼叫
(*pQI)(this, &iid, &ptr); // 等價寫法

巨集展開規則

  • 遞迴展開嵌套巨集(IUNK_VTABLE_OF 展開後再展開 QueryInterface)。
  • 避免自我嵌套導致無限展開。
  • 同名識別子在巨集本體中若非獨立語彙(如字串常量一部分)不會再展開。

總結

純 C 實作 COM 揭示了物件模型的底層機制:虛函數表 + 显式 This 指標 + 參考計數 + 偏移計算。理解這層機制,不僅能在無 C++ 環境下實作二進位相容介面,更能深入除錯 C++ 物件佈局、多重繼承指標調整、COM 聚合等進階主題。

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