COM(Component Object Model,組件對象模型)是一種跨應用程式與程式語言共享二進制代碼的標準。不同於 C++ 的原始碼重用,COM 定義了嚴格的二進制標準,規範模組(DLL/EXE)的內存佈局,使其獨立於任何程式語言(如 C++ 的名稱修飾)。編譯器負責生成符合標準的二進制代碼,讓後續開發者能從任何語言輕鬆調用。
Windows 核心即大量使用 DLL 共享代碼(kernel32.dll、user32.dll 等),但傳統 DLL 針對 C 語言介面,使用受限。MFC 擴展 DLL 僅限 MFC 程式。COM 解決了這些限制。雖理論上可移植至 Unix,但實際上主要見於 Windows 平台。
| 術語 | 定義 |
|---|---|
| 介面 | 一組純虛擬函數(方法)的集合,命名以 I 開頭(如 IShellLink)。介面支援單繼承,不支援多重繼承。 |
| Coclass(組件對象類) | 包含一或多個介面實現代碼的類,存在於 DLL 或 EXE 中。COM 對象是 Coclass 在內存中的實例。注意:COM「類」≠ C++「類」。 |
| COM 服務器 | 包含一或多個 Coclass 的二進制檔(DLL 或 EXE)。 |
| 註冊/反註冊 | 在註冊表建立/刪除服務器位置信息的過程。 |
| GUID / UUID | 128 位全球唯一識別碼。每個介面與 Coclass 擁有唯一 GUID,避免名稱衝突。CLSID 識別 Coclass,IID 識別介面。 |
| HRESULT | 32 位有號整數,非負表示成功,負值表示失敗。包含嚴重性、設施碼、狀態碼三個域。 |
| COM 庫 | 作業系統中負責 COM 運行支援的部分,常作為 COM 的同義詞。 |
CoCreateInstance()HRESULT CoCreateInstance(
REFCLSID rclsid, // Coclass 的 CLSID
LPUNKNOWN pUnkOuter, // 聚合用,通常為 NULL
DWORD dwClsContext, // 服務器類型,進程內用 CLSCTX_INPROC_SERVER
REFIID riid, // 請求介面的 IID
LPVOID* ppv // 返回介面指標地址
);
範例:建立 Shell Link 對象並取得 IShellLink 介面:
HRESULT hr;
IShellLink* pISL;
hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
IID_IShellLink, (void**)&pISL);
if (SUCCEEDED(hr)) {
// 使用 pISL 呼叫方法
} else {
// 建立失敗,hr 為錯誤碼
}
SUCCEEDED/FAILED 宏用於檢查 HRESULT。
Release()COM 對象維護自身引用計數。呼叫 Release() 通知對象「用完了」,計數歸零時對象自我釋放。必須在用畢介面後呼叫 Release(),否則 DLL 残留內存導致洩漏。長期運行程式應在閒置時呼叫 CoFreeUnusedLibraries() 卸載無引用的服務器。
IUnknown所有 COM 介面皆繼承自 IUnknown,提供三大核心方法:
AddRef():增加引用計數(複製介面指標時必須呼叫)。Release():減少引用計數,歸零時銷毀對象。QueryInterface(REFIID iid, void** ppv):請求對象的其他介面指標。若 Coclass 實作多個介面,必須透過此方法取得。範例:從 IShellLink 取得 IPersistFile:
IPersistFile* pIPF;
hr = pISL->QueryInterface(IID_IPersistFile, (void**)&pIPF);
if (SUCCEEDED(hr)) {
// 使用 pIPF...
pIPF->Release();
}
COM 方法返回的字串均為 Unicode(雙位元組字符)。需轉換為 TCHAR 或 ANSI 以便操作。常用方法:
WideCharToMultiByte():完整 API,支援代碼頁與複合字符處理。wcstombs():CRT 簡化版,內部呼叫上述 API。CString(MFC):建構子/指派運算子直接接受 Unicode 字串。ATL 宏:OLE2A()(OLE/COM 字串轉 ANSI)、W2T()(轉 TCHAR)等。需搭配 USES_CONVERSION 與 lstrcpy() 取得副本。若保持 Unicode,控制台程式可用 std::wcout 輸出,字串常量加 L 前綴。但 Win9x 環境下 Windows API 不接受 Unicode,跨平台仍建議用 TCHAR。
IActiveDesktop)WCHAR wszWallpaper[MAX_PATH];
HRESULT hr;
IActiveDesktop* pIAD;
CoInitialize(NULL);
hr = CoCreateInstance(CLSID_ActiveDesktop, NULL, CLSCTX_INPROC_SERVER,
IID_IActiveDesktop, (void**)&pIAD);
if (SUCCEEDED(hr)) {
hr = pIAD->GetWallpaper(wszWallpaper, MAX_PATH, 0);
if (SUCCEEDED(hr))
wcout << L"Wallpaper path is:\n" << wszWallpaper << endl;
else
cout << "GetWallpaper() failed." << endl;
pIAD->Release();
} else {
cout << "CoCreateInstance() failed." << endl;
}
CoUninitialize();
IShellLink + IPersistFile)
CString sWallpaper = wszWallpaper; // 轉 ANSI
IShellLink* pISL;
IPersistFile* pIPF;
CoInitialize(NULL);
hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
IID_IShellLink, (void**)&pISL);
if (SUCCEEDED(hr)) {
hr = pISL->SetPath(sWallpaper);
if (SUCCEEDED(hr)) {
hr = pISL->QueryInterface(IID_IPersistFile, (void**)&pIPF);
if (SUCCEEDED(hr)) {
hr = pIPF->Save(L"C:\\wallpaper.lnk", FALSE);
pIPF->Release();
}
}
pISL->Release();
}
CoUninitialize();
HRESULT 32 位結構:嚴重性位(成功/失敗)、設施碼(來源組件)、狀態碼。常見命名規範:設施_嚴重性_描述(如 REGDB_E_READREGDB)。通用成功碼 S_OK 無設施碼。
兩大查找工具:
0x800401F0)即得描述(「尚未呼叫 CoInitialize」)。hres,hr 直接顯示錯誤描述。

本文涵蓋 COM 核心概念、術語、對象生命週期管理、IUnknown 三大方法、字串處理技巧、實戰代碼範例與 HRESULT 錯誤處理。掌握這些基礎,即可開始使用 Windows 內建或第三方 COM 組件。下一篇將深入 COM 服務器內部機制,教學如何編寫自訂介面與服務器。