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

COM 技術深度探索 (一):從 C++ 到 DLL 再到 COM — 核心概念與極簡 COM 物件模型

     編輯:關於VC++

COM 是什麼?

"COM IS LOVE" — Don Box

Component Object Model (組件物件模型) 是微軟制定的二進位元件標準,解決 C++ 庫重用、版本管理、跨語言互操作三大痛點。

演進脈絡:C++ → DLL → COM

階段 1:靜態 C++ 類別庫

  • 類別 CMath (四則運算) 編譯進每個應用程式。
  • 三個應用程式各佔 4MB 程式碼 → 同時執行佔 12MB 虛擬記憶體。
  • 庫有 Bug 修正 → 所有應用程式必須重新編譯部署。

階段 2:動態連結庫 (DLL)

  • CMath 方法匯出至 DLL,連結器產生 Import Library。
  • 應用程式僅含 Stub,執行時動態載入 DLL,共享程式碼頁面。
  • 優點:記憶體共享、獨立更新 DLL。
  • 缺點:C++ 類別「介面與實作綁定」,名稱修飾、類別版面配置、繼承關係皆編譯器相關,跨語言/跨編譯器極難互操作。

階段 3:COM — 介面與實作分離

  • 定義純虛擬介面類別 (ISimpleMath, IAdvancedMath) — 二進位介面契約。
  • 實作類別 CMath 繼承介面 — 實作細節封裝於 DLL 內部。
  • 客戶端僅持有介面指標 (ISimpleMath*),透過 QueryInterface 導航。
  • 二進位相容:介面虛函數表 (vtable) 布局固定,任何語言只要能呼叫函數指標即可使用。

COM 基石

1. HRESULT 錯誤碼

typedef LONG HRESULT;
// 位元佈局: S(1bit) | Reserved | Facility(16bit) | Code(16bit)
#define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0)
#define FAILED(hr)    (((HRESULT)(hr)) < 0)

2. IDL (Interface Definition Language)

標準化介面描述,MIDL 編譯產生:Type Library (.tlb)、C/C++ Header (.h)、Proxy/Stub 代碼、GUID 定義 (.c)。

[
    local, object,
    uuid(00000000-0000-0000-C000-000000000046),
    pointer_default(unique)
]
interface IUnknown
{
    HRESULT QueryInterface([in] REFIID riid, [out, iid_is(riid)] void** ppvObject);
    ULONG AddRef();
    ULONG Release();
}
  • local:禁止產生網路編組碼 (本機專用)。
  • object:標記為 COM 介面。
  • uuid:GUID 唯一識別。
  • pointer_default(unique):允許 NULL 指標。
  • [in]/[out]:參數方向屬性。
  • iid_is(riid):ppvObject 型別由 riid 決定。

3. IUnknown — 萬惡之源 / 所有介面之母

所有 COM 介面直接/間接繼承 IUnknown,三大方法:

  • QueryInterface(REFIID, void**):導航至其他介面。
  • AddRef():引用計數 +1。
  • Release():引用計數 -1,歸零自刪。

極簡 COM 物件 — 四個檔案

Interface.h — 介面定義 (純 C++, 無 IDL)

#include 
// {7C8027EA-A4ED-467c-B17E-1B51CE74AF57}
static const GUID IID_ISimpleMath = {0x7c8027ea, 0xa4ed, 0x467c, {0xb1,0x7e,0x1b,0x51,0xce,0x74,0xaf,0x57}};
// {CA3B37EA-E44A-49b8-9729-6E9222CAE84F}
static const GUID IID_IAdvancedMath = {0xca3b37ea, 0xe44a, 0x49b8, {0x97,0x29,0x6e,0x92,0x22,0xca,0xe8,0x4f}};

interface ISimpleMath : IUnknown
{
    virtual int Add(int nOp1, int nOp2) = 0;
    virtual int Subtract(int nOp1, int nOp2) = 0;
    virtual int Multiply(int nOp1, int nOp2) = 0;
    virtual int Divide(int nOp1, int nOp2) = 0;
};

interface IAdvancedMath : IUnknown
{
    virtual int Factorial(int nOp1) = 0;
    virtual int Fabonacci(int nOp1) = 0;
};

Math.h — 實作類別宣告

#include "Interface.h"
class CMath : public ISimpleMath, public IAdvancedMath
{
private:
    ULONG m_cRef;
    int calcFactorial(int n);
    int calcFabonacci(int n);
public:
    CMath();
    STDMETHOD(QueryInterface)(REFIID riid, void** ppv);
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();
    int Add(int nOp1, int nOp2);
    int Subtract(int nOp1, int nOp2);
    int Multiply(int nOp1, int nOp2);
    int Divide(int nOp1, int nOp2);
    int Factorial(int nOp);
    int Fabonacci(int nOp);
};

Math.cpp — 實作

#include "Interface.h"
#include "Math.h"

STDMETHODIMP CMath::QueryInterface(REFIID riid, void** ppv)
{
    if (riid == IID_ISimpleMath) *ppv = static_cast(this);
    else if (riid == IID_IAdvancedMath) *ppv = static_cast(this);
    else if (riid == IID_IUnknown) *ppv = static_cast(this);
    else { *ppv = 0; return E_NOINTERFACE; }
    reinterpret_cast(*ppv)->AddRef();
    return S_OK;
}
STDMETHODIMP_(ULONG) CMath::AddRef() { return ++m_cRef; }
STDMETHODIMP_(ULONG) CMath::Release()
{
    ULONG res = --m_cRef;
    if (res == 0) delete this;
    return res;
}
int CMath::Add(int a, int b) { return a+b; }
int CMath::Subtract(int a, int b) { return a-b; }
int CMath::Multiply(int a, int b) { return a*b; }
int CMath::Divide(int a, int b) { return a/b; }
int CMath::calcFactorial(int n) { return n<=1?1:n*calcFactorial(n-1); }
int CMath::Factorial(int n) { return calcFactorial(n); }
int CMath::calcFabonacci(int n) { return n<=1?1:calcFabonacci(n-1)+calcFabonacci(n-2); }
int CMath::Fabonacci(int n) { return calcFabonacci(n); }
CMath::CMath() : m_cRef(0) {}

Simple.cpp — 客戶端測試

#include "Math.h"
#include 
using namespace std;

int main()
{
    CMath* pMath = new CMath; // 直接 new,非標準 COM 建立流程
    ISimpleMath* pSimple = NULL;
    IAdvancedMath* pAdv = NULL;

    pMath->QueryInterface(IID_ISimpleMath, (void**)&pSimple);
    if (pSimple) cout << "10 + 4 = " << pSimple->Add(10, 4) << endl;

    pSimple->QueryInterface(IID_IAdvancedMath, (void**)&pAdv);
    if (pAdv) cout << "10 Fabonacci = " << pAdv->Fabonacci(10) << endl;

    pAdv->Release(); pSimple->Release(); // 釋放介面,物件隨之刪除
    return 0;
}

物件二進位結構圖

Math 組件二進位結構圖

  • CMath 物件開頭為兩個 vptr (分別指向 ISimpleMath、IAdvancedMath vtable)。
  • QueryInterface 透過 static_cast 調整指標位移,回傳正確 vptr。
  • 引用計數 m_cRef 屬於物件整體,非單一介面。

小結

  • 此例符合 COM 最小需求 (實作 IUnknown),但非 DLL、非標準啟動 (無 CoCreateInstance)、無註冊機制。
  • 下一篇將把它封裝成標準 DLL 型 COM 元件,實作 DllGetClassObject、DllRegisterServer、IDL 定義與類別工廠。

本文配套原始碼 (四檔案極簡模型) 可供下載。

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