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

COM 組件物件模型核心規範完整參考:IUnknown、介面設計、記憶體管理、參考計數、聚合、單元執行緒模型

     編輯:關於C++

核心原則

任何 COM 物件至少實作 IUnknown。無 IUnknown 即非 COM 物件。

介面設計規範

  • 直接/間接繼承 IUnknown
  • 唯一識別碼 IID (GUID)
  • 介面不可變:一旦發布 IID,定義永不可更改
  • 方法回傳 HRESULT (支援 RPC 錯誤傳播)
  • 字串參數使用 Unicode (BSTR)

IUnknown 實作契約

1. 物件同一性

同一物件任意介面 QueryInterface(IID_IUnknown) 回傳相同物理指標,可用於比較兩介面是否屬於同一物件。

2. 靜態介面集合

若 QI(IID_X) 成功,後續呼叫必定成功;若失敗,後續必定失敗。介面集合不隨執行期動態變化。

3. 完整性 (自反、對稱、傳遞)


pA->QI(IID_IA, ...)     // 自反:必成功
pA->QI(IID_IB, &pB)     // pB 成功取得
pB->QI(IID_IA, ...)     // 對稱:必成功
pB->QI(IID_IC, &pC)     // pC 成功取得
pA->QI(IID_IC, ...)     // 傳遞:必成功

4. 參考計數容量

AddRef/Release 使用 32-bit 無號整數,支援至 2^31-1。

5. Release 不代表失敗

資源釋放語意需透過更高層介面方法表達,Release 僅遞減計數。

記憶體管理規則

錯誤回傳時的額外規則

  • [out] 參數必須設為可安全清理狀態 (通常 NULL)
  • [in,out] 參數保持呼叫端初始值,或設為錯誤狀態
  • 所有 [out] 指標參數在函數入口即設 NULL

參考計數規則

規則 1:每個新介面指標副本 → AddRef;每個銷毀 → Release

例外情況 (可省略 AddRef/Release)

  1. [in]/[out] 參數:呼叫端已持有,被呼叫端存 [out] 時會 Release 舊值 → 呼叫端需預先 AddRef
  2. 全域變數取值:區域副本需獨立 AddRef (全域可能被銷毀)
  3. 合成新指標 (如 QueryInterface 實作、Class Factory) → 初始 AddRef
  4. 內部儲存指標回傳:回傳前 AddRef (被呼叫端不知生命週期)

規則 2:特定生命週期知識可省略 AddRef/Release

例如函數內部暫時使用,明確知曉無並發銷毀風險。

重要提醒

  • 客戶端視角:參考計數屬於介面,非物件。不同介面可能有不同計數。
  • 勿依賴 AddRef/Release 回傳值 (僅供除錯)。
  • 指標穩定化:參見 OLE 說明 Stabilizing the this Pointer。

COM 程式庫初始化責任

每個進程 (客戶/伺服器/物件執行者) 必須:

  1. 確認版本 CoBuildVersion
  2. CoInitialize/CoInitializeEx 初始化
  3. CoUninitialize 反初始化

In-proc 伺服器可假設載入進程已完成上述步驟。

伺服器規範

參數類型分配者釋放者分配器
[in]呼叫端呼叫端任意
[out]被呼叫端呼叫端COM 標準 (CoTaskMemAlloc/Free)
[in,out]呼叫端呼叫端COM 標準 (被呼叫端可重新分配)
類型必須匯出/支援
In-proc (DLL)DllGetClassObject, DllCanUnloadNow, DllRegisterServer, DllUnregisterServer, 自註冊
Local (EXE)/RegServer, /UnRegServer 命令列參數

版本資源需含 OLESelfReg 字串。

聚合物件實作規範

內部物件

  • 獨立 IUnknown (Non-delegating) 控制內部介面參考計數
  • 其他介面 QI/AddRef/Release 全部 轉發給外部 IUnknown (Delegating)
  • 建立時若外部要求非 IUnknown 介面 → 回傳 CLASS_E_NOAGGREGATION

外部物件

  • 建立內部物件時明確 QI(IUnknown)
  • 保護重入:取得內部介面後 pOuter->Release(),釋放前 pOuter->AddRef()
  • 不盲目 QI 內部未知介面

巢狀類別實作範例


class CSomeObject : public IUnknown {
    DWORD m_cRef; IUnknown* m_pUnkOuter;
    class CImpSomeInterface : public ISomeInterface {
        IUnknown* m_pUnkOuter;
        STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
            return m_pUnkOuter->QueryInterface(riid, ppv); // 轉發
        }
        STDMETHODIMP_(ULONG) AddRef() { return m_pUnkOuter->AddRef(); }
        STDMETHODIMP_(ULONG) Release() { return m_pUnkOuter->Release(); }
        // ISomeInterface 方法...
    } m_ImpSomeInterface;

    // Non-delegating IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void** ppv) {
        if (riid == IID_IUnknown) *ppv = this;
        else if (riid == IID_ISomeInterface) *ppv = &m_ImpSomeInterface;
        else return E_NOINTERFACE;
        ((IUnknown*)*ppv)->AddRef(); return S_OK;
    }
    STDMETHODIMP_(ULONG) AddRef() { return ++m_cRef; }
    STDMETHODIMP_(ULONG) Release() { if (--m_cRef==0) { delete this; return 0; } return m_cRef; }

    static HRESULT Create(IUnknown* pUnkOuter, REFIID riid, void** ppv) {
        if (pUnkOuter && riid != IID_IUnknown) return CLASS_E_NOAGGREGATION;
        CSomeObject* p = new CSomeObject(pUnkOuter);
        if (!p) return E_OUTOFMEMORY;
        if (!pUnkOuter) p->m_pUnkOuter = (IUnknown*)p; // 非聚合時指向自己
        HRESULT hr = p->QueryInterface(riid, ppv);
        if (FAILED(hr)) delete p;
        return hr;
    }
};

單元執行緒模型 (Apartment Threading)

  • 每個物件駐留於單一單元 (執行緒)
  • 所有呼叫必須在物件所在單元執行 → 單元間指標需編列 (CoMarshalInterThreadInterfaceInStream 等)
  • 單元需訊息迴圈 (GetMessage/DispatchMessage) 或 MsgWaitForMultipleObjects
  • DLL 註冊 ThreadingModel=Apartment
  • DllGetClassObject 需支援多執行緒呼叫 (單元安全)
  • DllCanUnloadNow 由主單元呼叫
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved