COM(Component Object Model)是Windows平台上最核心的元件技術,DirectX、ADO、OLE、Windows Shell擴展等無數系統級功能皆建構於此。本文將從動態連結庫(DLL)切入,層層深入解析COM的二進制標準、介面設計、GUID識別機制、參考計數與IUnknown三大核心方法,並以C++代碼示範完整組件實作邏輯。
DLL是包含共享函數、資源的可執行檔,讓多行程共享同一份記憶體副本。連結方式分兩種:
.lib 導入庫解析符號.lib,或用 #pragma comment(lib, "xxx.lib")LoadLibrary() 載入、FreeLibrary() 釋放GetProcAddress() 取得函數位址,再以函數指標調用// 載入DLL
HMODULE LoadLibrary(LPCTSTR lpFileName);
// 釋放DLL
BOOL FreeLibrary(HMODULE hModule);
// 取得函數位址
FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpProcName);
// DLL進入點——每個DLL必須實作
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);
DllMain 的四種觸發時機(fdwReason):
| 值 | 觸發時機 |
|---|---|
DLL_PROCESS_ATTACH | DLL被載入進程 |
DLL_THREAD_ATTACH | 進程建立新執行緒 |
DLL_THREAD_DETACH | 執行緒結束 |
DLL_PROCESS_DETACH | DLL被卸載或進程結束 |
// 假設Math.dll導出 int Plus(int, int)
HMODULE hMod = LoadLibrary(_T("Math.dll"));
if (hMod) {
// 定義函數指標型別
typedef int (*PFN_PLUS)(int, int);
PFN_PLUS pPlus = (PFN_PLUS)GetProcAddress(hMod, "Plus");
if (pPlus) {
int result = pPlus(10, 20); // 呼叫DLL函數
printf("Result: %d\n", result);
}
FreeLibrary(hMod);
}
90年代Windows開發語言百家爭鳴(C++、VB、Delphi、C#...),二進制不相容導致元件無法跨語言複用。微軟定義基於二進制的通用介面規範,解決:
| C++概念 | COM概念 | 說明 |
|---|---|---|
| class | CoClass(組件類別) | 實作實體,含資料與方法 |
| public虛函數 | Interface(介面) | 純虛類別,定義契約,無實作 |
| 繼承 | 多重繼承介面 | 一個CoClass實作多個介面 |
| this指標 | 介面指標 | 指向vtable的指標 |
// 定義介面(純虛類別,繼承IUnknown)
class IPhysiology : public IUnknown {
public:
virtual void Eat(Food*) = 0;
virtual void Drink(Liquid*) = 0;
virtual Waste* Toilet() = 0;
};
class IPsychics : public IUnknown {
public:
virtual Sound Laugh() = 0;
virtual Sound Cry() = 0;
};
class IDynamics : public IUnknown {
public:
virtual Speed Run() = 0;
virtual Speed Walk() = 0;
};
// 實作物件:多重繼承三個介面
class Human : public IPhysiology, public IPsychics, public IDynamics {
public:
// IUnknown實作
STDMETHOD(QueryInterface)(REFIID riid, void** ppv) override;
STDMETHOD_(ULONG, AddRef)() override;
STDMETHOD_(ULONG, Release)() override;
// 介面方法實作...
void Eat(Food* f) override { printf("Delicious!\n"); }
Sound Laugh() override { return Sound("Haha"); }
Speed Run() override { return Speed(20); }
private:
ULONG m_ref = 0;
};
typedef struct _GUID {
DWORD Data1;
WORD Data2;
WORD Data3;
BYTE Data4[8];
} GUID;
// C++定義方式
const GUID CLSID_Human =
{0xABFA7022, 0x7E2F, 0x4D0E, {0x8A,0x4F,0xF5,0x8B,0xBC,0xEB,0xB2,0xDA}};
// 註冊表/IDL字串形式
// {ABFA7022-7E2F-4D0E-8A4F-F58BBCEBB2DA}
CLSID_ 前綴:組件類別識別碼IID_ 前綴:介面識別碼guidgen.exe 或VS工具產生class IUnknown {
public:
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef() = 0;
virtual ULONG STDMETHODCALLTYPE Release() = 0;
};
S_OK,*ppv 指向該介面E_NOINTERFACE,*ppv = NULLdelete this 自毀多客戶端共享同一物件時,傳統 delete 會導致懸空指標崩潰。COM規定:
AddRef()QueryInterface 成功內部自動 AddRef()Release()HRESULT Human::QueryInterface(REFIID riid, void** ppv) {
if (riid == IID_IUnknown || riid == IID_IPhysiology) {
*ppv = static_cast(this);
} else if (riid == IID_IPsychics) {
*ppv = static_cast(this);
} else if (riid == IID_IDynamics) {
*ppv = static_cast(this);
} else {
*ppv = NULL;
return E_NOINTERFACE;
}
AddRef(); // 關鍵:成功時必須AddRef
return S_OK;
}
ULONG Human::AddRef() {
return InterlockedIncrement(&m_ref); // 執行緒安全
}
ULONG Human::Release() {
ULONG ref = InterlockedDecrement(&m_ref);
if (ref == 0) delete this;
return ref;
}
微軟強制要求所有COM介面實作遵循:
QueryInterface(IID_IUnknown) 必須回傳相同指標值,可用於判斷「是否同一物件」pA->QueryInterface(IID_IA, &pB) 必須成功pA->QI(IB, &pB) 成功,則 pB->QI(IA, &pA) 必成功介面定義語言(IDL)用於跨語言描述COM類型,經 midl.exe 編譯產生 .tlb 類型庫:
[
object, uuid(6AAF876E-FCED-4ee0-B5D3-63CD6E2242F5),
pointer_default(unique)
]
interface IPhysiology : IUnknown {
void Eat([in] Food* in);
void Drink([in] Liquid* in);
Waste* Toilet();
};
[
uuid(ABFA7022-7E2F-4D0E-8A4F-F58BBCEBB2DA),
version(1.0)
]
library HumanLib {
importlib("stdole2.tlb");
[
uuid(62FD0E39-DA84-4B19-BAB0-960A27AC2B71)
]
coclass Human {
[default] interface IPhysiology;
interface IPsychics;
interface IDynamics;
};
}

COM物件結構示意圖:CoClass實作多介面,透過IUnknown導航
本文完整建立了:DLL載入機制 → COM二進制標準 → 介面/物件/GUID/IUnknown → 參考計數 → 介面規範 → IDL描述 的知識鏈。下一篇將實戰演示:
DllGetClassObject 導出函數CoCreateInstance 內部流程作者:釋雪 | MSN: Blue_Atlantis400@hotmail.com | QQ: 63068279