COM (Component Object Model) 是 Microsoft 提出的元件標準,不僅定義元件程式間交互的標準,也提供元件執行所需的環境。在 COM 標準中,一個元件程式稱為一個模組,可以是動態連結庫 (DLL),稱為進程內元件;也可以是可執行程式 (EXE),稱為進程外元件。一個元件程式可包含一或多個元件物件。COM 以物件為基本單元,程式間通訊的雙方是元件物件 (COM 物件),而元件程式是提供 COM 物件的程式碼載體。
COM 物件不同於 C++ 等語言的物件概念:COM 物件建立在二進位可執行碼層級,語言無關;C++ 物件建立在原始碼層級。這使不同程式語言開發的元件能相互交互。
| 模型 | 說明 | 優點 | 缺點 |
|---|---|---|---|
| 進程內 (In-process) | DLL 載入客戶端進程空間 | 效率高,無進程間通訊開銷 | 元件不穩定會導致客戶端當機 |
| 本地進程外 (Local Server) | 同機器獨立 EXE 進程 | 穩定性好,一個元件可服務多客戶端 | 開銷大,呼叫效率較低 |
| 遠端進程外 (Remote Server) | 不同機器,可為 DLL 或 EXE | 分散式部署 | 網路延遲,複雜度高 |
對客戶端程式而言,這三種模型是透明的,使用方式一致。
假設物件 1 (外部物件) 想重用物件 2 (內部物件) 功能:
128-bit 隨機數,結構定義:
typedef struct _GUID {
DWORD Data1;
WORD Data2;
WORD Data3;
BYTE Data4[8];
} GUID;
範例:{64BF4372-1007-B0AA-444553540000}。Visual C++ 提供 UUIDGen.exe、GUIDGen.exe 產生 GUID;COM 函式庫提供 CoCreateGuid API。
技術上介面是包含函數指標的資料結構 (虛函數表 vtable)。介面指標 → pVtable → 函數指標陣列 → 物件實作。每個介面成員函數第一個參數為 this 指標。COM 規範要求使用 Unicode (BSTR),若內部用 ANSI 需轉換。Visual C++ 提供 _com_util::ConvertStringToBSTR / ConvertBSTRToString。
COM 採用擴展的 OSF DCE IDL 描述介面,不依賴特定語言。MIDL 編譯器可將 .idl 編譯為 C/C++ 相容的 .h 檔。IDL 可定義參數類型、輸入輸出屬性、可變長度陣列等。
interface IUnknown {
HRESULT QueryInterface([in] REFIID iid, [out] void **ppv);
ULONG AddRef(void);
ULONG Release(void);
}
三大核心規則:
HKEY_CLASSES_ROOT\CLSID...CLSID\{GUID}\InprocServer32...CLSID\{GUID}\LocalServer32HKEY_CLASSES_ROOT\Interface...CLSID\{GUID}\ProxyStubClsid32HKEY_CLASSES_ROOT\TypeLibHKEY_CLASSES_ROOT\ProgID (如 COMCTL.TreeCtrl)類別工廠是 COM 物件的生產基地,支援 IClassFactory 介面:
interface IClassFactory : IUnknown {
HRESULT CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv);
HRESULT LockServer(BOOL fLock);
}
DLL 匯出函數 DllGetClassObject 建立類別工廠:
HRESULT DllGetClassObject(REFCLSID clsid, REFIID iid, void **ppv);
COM 程式庫調用流程:CoCreateInstance → CoGetClassObject → DllGetClassObject → IClassFactory::CreateInstance。
CoGetClassObject:取得類別工廠介面指標,支援遠端伺服器資訊。CoCreateInstance:封裝 CoGetClassObject + CreateInstance,最常用,不支援遠端建立。CoCreateInstanceEx:支援遠端建立與一次取得多介面指標 (MULTI_QI 陣列)。HRESULT CoInitialize(LPVOID pvReserved); // 必須在呼叫 COM API 前呼叫
void CoUninitialize(void); // 程式結束前呼叫,配對使用
記憶體管理介面 IMalloc 及封裝 API:CoTaskMemAlloc、CoTaskMemFree、CoTaskMemRealloc。跨模組記憶體配置/釋放必須使用 COM 記憶體管理器 (如 ProgIDFromCLSID 分配的字串需 CoTaskMemFree 釋放)。
LoadLibrary → DllGetClassObject。卸載條件:物件數=0 且類別工廠鎖計數=0,DllCanUnloadNow 傳回 TRUE。客戶端定期呼叫 CoFreeUnusedLibraries 觸發卸載。CreateProcess /Embedding → CoRegisterClassObject。進程退出條件類似,但需處理使用者介面互動 (旗標控制)。32-bit:嚴重性 (2 bits) + 客戶位 (1 bit) + 設施代碼 (12 bits) + 錯誤代碼 (16 bits)。常用值:S_OK (0)、S_FALSE (1)、E_FAIL、E_OUTOFMEMORY、E_NOINTERFACE。判斷成功/失敗必須用 SUCCEEDED/FAILED 巨集,不可直接比較 S_OK。
pUnkOuter 參數與內部物件的特殊參考計數處理。關鍵標頭檔:Unknwn.h、Wtypes.h、Objidl.h、Comdef.h、ObjBase.h、Ole2.h。常用巨集:DECLARE_INTERFACE、DECLARE_INTERFACE_、STDMETHOD、STDMETHOD_。