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

COM 程式設計入門:核心概念、介面操作與實戰範例

     編輯:關於VC++

什麼是 COM?

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 / UUID128 位全球唯一識別碼。每個介面與 Coclass 擁有唯一 GUID,避免名稱衝突。CLSID 識別 Coclass,IID 識別介面。
HRESULT32 位有號整數,非負表示成功,負值表示失敗。包含嚴重性、設施碼、狀態碼三個域。
COM 庫作業系統中負責 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 與 ANSI 轉換

COM 方法返回的字串均為 Unicode(雙位元組字符)。需轉換為 TCHAR 或 ANSI 以便操作。常用方法:

  1. WideCharToMultiByte():完整 API,支援代碼頁與複合字符處理。
  2. wcstombs():CRT 簡化版,內部呼叫上述 API。
  3. CString(MFC):建構子/指派運算子直接接受 Unicode 字串。
  4. ATL 宏:OLE2A()(OLE/COM 字串轉 ANSI)、W2T()(轉 TCHAR)等。需搭配 USES_CONVERSION 與 lstrcpy() 取得副本。

若保持 Unicode,控制台程式可用 std::wcout 輸出,字串常量加 L 前綴。但 Win9x 環境下 Windows API 不接受 Unicode,跨平台仍建議用 TCHAR。

實戰範例:單介面與多介面對象

範例 1:取得桌面牆紙路徑(單介面 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();

範例 2:建立牆紙捷徑(多介面 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 深度解析與錯誤查找

HRESULT 32 位結構:嚴重性位(成功/失敗)、設施碼(來源組件)、狀態碼。常見命名規範:設施_嚴重性_描述(如 REGDB_E_READREGDB)。通用成功碼 S_OK 無設施碼。

兩大查找工具:

  1. VC++ 錯誤查找工具:輸入十六進位值(如 0x800401F0)即得描述(「尚未呼叫 CoInitialize」)。
  2. 偵錯器 Watch 視窗:輸入 hres,hr 直接顯示錯誤描述。

錯誤查找工具截圖

偵錯器 Watch 視窗截圖

總結

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

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