"COM IS LOVE" — Don Box
Component Object Model (組件物件模型) 是微軟制定的二進位元件標準,解決 C++ 庫重用、版本管理、跨語言互操作三大痛點。
CMath (四則運算) 編譯進每個應用程式。CMath 方法匯出至 DLL,連結器產生 Import Library。ISimpleMath, IAdvancedMath) — 二進位介面契約。CMath 繼承介面 — 實作細節封裝於 DLL 內部。ISimpleMath*),透過 QueryInterface 導航。typedef LONG HRESULT;
// 位元佈局: S(1bit) | Reserved | Facility(16bit) | Code(16bit)
#define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0)
#define FAILED(hr) (((HRESULT)(hr)) < 0)
標準化介面描述,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 決定。所有 COM 介面直接/間接繼承 IUnknown,三大方法:
QueryInterface(REFIID, void**):導航至其他介面。AddRef():引用計數 +1。Release():引用計數 -1,歸零自刪。#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;
};
#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);
};
#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) {}
#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;
}

CMath 物件開頭為兩個 vptr (分別指向 ISimpleMath、IAdvancedMath vtable)。QueryInterface 透過 static_cast 調整指標位移,回傳正確 vptr。m_cRef 屬於物件整體,非單一介面。IUnknown),但非 DLL、非標準啟動 (無 CoCreateInstance)、無註冊機制。DllGetClassObject、DllRegisterServer、IDL 定義與類別工廠。本文配套原始碼 (四檔案極簡模型) 可供下載。