int
main(
int
argc,
char
*argv[] )
{
cout <<
"Initializing COM"
<< endl;
if
( FAILED( CoInitialize( NULL )))
{
cout <<
"Unable to initialize COM"
<< endl;
return
-1;
}
ISampleMath* pSampleMath;
HRESULT
hr;
// This time use CoCreateInstance
hr = CoCreateInstance( CLSID_SampleMath,
NULL,
CLSCTX_LOCAL_SERVER,
IID_ISampleMath,
(
void
**) &pSampleMath );
if
( FAILED( hr ))
{
cout <<
"Failed to create server instance. HR = "
<< hr << endl;
CoUninitialize();
return
-1;
}
// Access the IMath interface
long
lResult;
pSampleMath->Add( 134, 353, &lResult );
cout <<
"134 + 353 = "
<< lResult << endl;
// Release all of our interfaces
if
( pSampleMath )
CoUninitialize();
return
0;
}
COM組件可以是一個Dll(進程內組件),也可以是一個EXE(進程外組件)。進程內組件就是組件和客戶程序在同一進程內,進程外組件即組件和客戶程序分別有自己的進程空間。
一個COM組件可以包含多個COM對象,一個COM對象又可以有多個接口。
第2章 COM對象和接口
2.1 CLSID和IID
對於COM對象來說,接口是它與外界進行交互的唯一途徑。
每個COM對象,可以用CLSID來標識,COM對象的每個接口可以用IID來標識。CLSID和IID都是128位的標識符GUID,是一個隨機數,可以由兩方面特性來保證:空間(如網絡適配器地址)和時間。
GUID可以通過COM庫的API函數生成:
HRESULT CoCreateGuid( GUID * pguid );
2.1.2 COM對象和C++對象的不同
COM對象的數據成員封裝以組建模塊為最終邊界,對於對象用戶是完全透明的、不可見的,用戶必須通過接口方法來訪問數據成員;C++對象的封裝特性只是語義上的,用戶可以直接看到數據成員。
2.2 COM接口結構
接口是包含了一組函數的數據結構。客戶程序利用這些函數獲得組件對象的服務。接口成員函數中的字符串變量必須使用Unicode字符指針。
客戶程序用一個指向接口數據結構的指針來調用接口成員函數,接口指針又指向pVtable(指向vtable的指針),pVtable指向一組函數,稱為接口函數表(虛函數表vtable),表中每一項為4個字節長的函數指針,每個函數指針再指向函數的具體實現。
2.2.2 接口描述語言IDL
2.3 IUnknown接口
COM定義的每個接口都必須從IUnknown接口繼承過來,因為IUnknown接口提供了兩個重要特性:生存期控制和接口查詢。

QueryInterface用於查詢COM對象的其他接口指針,AddRef和Release用於對引用計數進行操作。
在COM對象級實現引用計數,精細度比較合適。
2.3.1 使用引用計數規則
(1)函數的參數中使用接口指針變量。
輸入參數:在被調用函數中,不必調用AddRef和Release函數。
輸出函數:在被調用函數返回之前,對輸出參數調用AddRef,增加引用計數。
輸入-輸出參數:在參數被修改之前,對原來傳進來的接口指針調用Release,引用計數減1,在參數被修改之后,對新的接口指針變量指針調用AddRef,若在函數執行過程中,參數沒有被修改,則不必調用AddRef和Release函數。
(2)局部接口指針變量。
COM的實現
3.1 進程內組件(DLL)的實現,可以參考DLL技術,主要參數為:
1)LoadLibrary:裝載DLL模塊
2)GetProcAddress:取引出函數的地址
3)FreeLibrary:釋放DLL模塊
COM采用LPC(本地過程調用)和RPC(遠程過程調用)的方法進行進程之間的通信,LPC用於在同一機器上的不同進程之間進行通信,而RPC用於在不同機器上的進程之間進行通信。
3.2 組件程序的兩個用於注冊的入口函數為DllRegisterServer和DllUnregisterServer,注冊組件使用命令:RegSvr32 *.dll;反注冊組件使用命令:RegSvr32 /u *.dll,進程內組件注冊使用此命令。進程外組件注冊必須支持兩個命令行參數/RegServer 和/UnregServer。
3.3 類廠
COM庫通過類廠創建COM對象,對應每一個COM類,都有一個類廠專門用於該COM類的對象創建工作。類廠本身也是一個COM對象,它支持接口IClassFactory。
CreateInstance創建對應的COM對象,LockServer控制組件的生存周期。
類廠由函數DllGetClassObject創建。
DllGetClassObject返回類廠對象的接口指針,再通過CreateInstance創建對應的COM對象。
3.3.2 COM庫與類廠的交互
創建對象函數:
1)若創建遠程對象或希望一次獲取對象的多個接口指針,選用CoCreateInstanceEx。
2)若希望獲取類廠對象或要調用類廠的某些成員函數,選用CoGetClassObject,通常IID=IID_IclassFactory,進程內組件直接調用DLL的CoGetClassObject,若CoGetClassObject創建的類廠對象位於進程外組件,函數啟動組件進程,然后等待,直到組件進程把它支持的COM類對象的類廠注冊到COM中,返回類廠信息。
3)其他情況下,選用CoCreateInstance創建對象,CoCreateInstance封裝類廠創建對象的過程,返回COM對象的接口指針,不能創建遠程機器上的對象。
COM庫初始化函數:CoInitialize
COM庫終止函數:CoUninitialize
3.4.2 COM庫的內存管理
COM提供的內存管理器標准,是一個COM接口IMalloc。當組件內存的分配和釋放不在同一模塊,需要用到內存管理器,COM庫封裝了三個API函數,用於內存分配和釋放
CoTaskMemAlloc
CoTaskMemFree
CoTaskMemRealloc
3.4.3 組件程序的裝載和卸載
1)進程內組件的裝載、卸載
2)進程外組件的裝載、卸載
組件程序滿足兩個條件才可以被卸載:組件中對象數為0;類廠的鎖計數器為0。此時,DllCanUnloadNow返回TRUE。
在判斷返回類型HRESULT時,需用宏SUCCEEDED和FAILED。