第1章 組件
1、COM,即組件對象模型,是關于如何建立組件以及如何通過組件建構應用程序的一個規范。
2、組件的優點:應用程序可隨時間的流逝而發展變化;定制應用程序;組件庫;分布式組件。
3、對組件的需求:組件必須動態連接;必須隱藏其內部實現細節。
4、COM組件是以Win32動態鏈接庫(DLLs)或可執行文件(EXEs)的形式發布的可執行代碼組成
的。遵循COM規范編寫的組件將能夠滿足對組件家夠的所有需求。COM組件是動態鏈接的,COM使
用DLL將組件動態鏈接起來。對于COM組件的封裝是很容易的。COM組件按照一種標準的方式來宣
布他們的存在。COM組件是一種給其他應用程序提供面向對象的API或服務的極好方法。
5、COM并不是一種計算機語言。
6、將COM同DLL相提并論是不合適的。實際上COM使用了DLL來給組件提供動態鏈接的能力。
7、COM并不是像Win32API那樣的函數集,它更主要的是一種編寫能夠按面向對象API形式提供服
務的組件的方法。
8、COM并不是類似于MFC這樣的C++類庫。COM給開發人員提供的是一種開發與語言無關的組件庫
的方法,但COM本身并沒有提供任何實現。
9、COM具有一個被稱作是COM庫的API,它提供的是對所有客戶及組件都非常有用的組件管理服務
。
////////////////////////////////////////////////////////////////////
第2章 接口
1、在COM中接口就是一切。
(1)接口可以保護系統免首外界變化的影響。
(2)接口可以使客戶用同樣的方式來處理不同的組件。
2、(1)COM接口在C++中是用純抽象基類實現的。
?? (2)一個COM組件可以提供多個接口。
?? (3)一個C++類可以使用多繼承來實現一個可以提供多個接口的組件。
3、類并非組件。
4、接口并非總是繼承的。對接口的繼承只不過是一種實現細節而已。除了可以使用一個類來實
現幾個不同的接口外,還可以用單個的類來實現每一個接口再使用指向這些類的指針。
5、組件可以支持任意數目的接口。為支持多重接口,可以使用多重繼承。支持多重接口的組件
可以被看作是接口的集合。
6、COM接口的不變性、多態以及接口繼承。
(1)一旦公布了一個接口,那么它將永遠保持不變。當對組件進行升級時,一般不會修改已有
的接口,而是加入一些新的接口。
(2)多態指的是可以按同一種方式來處理不同的對象。
7、虛擬函數表(vtbl):包含一組指向虛擬函數實現的指針。
定義一個純抽象基類也就是定義了相應的內存結構。但此內存只是在派生類中實現此抽象基類時
才會被分配。當派生類繼承一個抽象基類時,它將繼承此內存結構。
8、在COM中,對一個組件的訪問只能通過函數完成,而絕不能直接通過變量。
9、接口的真正的威力在于繼承此接口的所有類均可以被客戶按同一方式進行處理。
////////////////////////////////////////////////////////////
第3章 QueryInterface函數
1、接口查詢:
客戶同組件的交互都是通過一個接口完成的。在客戶查詢組件的其他接口時,也是通過接口完成
的。這個接口就是IUnknown。
IUnknown接口的定義包含在Win32 SDK中的UNKNOWN.H頭文件中。
interface IUnknown
{
??? virtual HRESULT _stdcall QueryInterface(const IID& iid,void **ppv) = 0;
??? virtual ULONG _stdcall AddRef() = 0;
??? virtual ULONG _stdcall Release() = 0;
}
在IUnknown中定義了一個名為QueryInterface的函數。客戶可以調用QueryInterface來決定組件
是否支持某個特定的接口。
2、所有的COM接口都需要繼承IUnknown。
3、由于所有的COM接口都繼承了IUnknown,每個接口的vtbl中的前三個函數都是
QueryInterface,AddRef和Release。若某個接口的vtbl中的前三個函數不是這三個,那么它將不
是一個COM接口。由于所有的接口都是從IUnknown 繼承的,因此所有的接口都支持
QueryInterface.因此組件的任何一個接口都可以被客戶用來獲取它所支持的其他接口。
4、非虛擬繼承:注意IUnknown并不是虛擬基類,所以COM接口并不能按虛擬方式繼承IUnknown,
這是由于會導致與COM不兼容的vtbl。若COM接口按虛擬方式繼承IUnknown,那么COM接口的vtbl
中的頭三個函數指向的將不是IUnknown的三個成員函數。
5、一個QuertyInterface可以用一個簡單的if-then-else語句實現,但case語句是無法用的,因
為接口標識符是一個結構而不是一個數。
6、多重類型及類型轉換
7、QueryInterface的規則
(1)QueryInterface返回的總是同一IUnknown指針。
(2)若客戶曾經獲取過某個接口,那么它將總能獲取此接口。
(3)客戶可以再次獲取已經擁有的接口。
(4)客戶可以從任何接口返回到起始接口。
(5)若能夠從某個借口獲取某特定接口,那么可以從任意接口都將可以獲取此接口。
8、接口的IID決定了它的版本。當改變了下列條件中的任何一個時,就應給新接口指定新的ID:
(1)接口中函數的數目。
(2)接口中函數的是順序。
(3)某個函數的參數。
(4)某個函數參數的順序。
(5)某個函數參數的類型。
(6)函數可能的返回值。
(7)函數參數的含義。
(8)接口中函數的含義。
9、避免違反隱含和約:
(1)使接口不論在其成員函數怎么被調用都能正常工作。
(2)強制客戶按一定的方式來使用此接口并在文檔中將這一點說明清楚。
//////////////////////////////////////////////////////////////////
第4章 引用計數
1、生命期控制
IUnknown的另外兩個成員函數AddRef和Release的作用就是給客戶提供一種讓它指示何時處理完
一個接口的手段。
2、AddRef和Release實現的是一種名為引用計數的內存管理技術。
引用計數是使組件能夠自己將自己刪除的最簡單同時也是效率最高的方法。
COM組件將維護一個稱做是引用計數的數值。當客戶從組件取得一個接口時,此引用計數值將增1
。當客戶使用完某個接口后,組件的引用計數值將減1。當引用計數值為0時,組件即可將自己從
內存中刪除。
3、正確使用引用計數規則:
(1)在返回之前調用AddRef。對于那些返回接口指針的函數,在返回之前應用相應的指針調用
AddRef。這些函數包括QueryInterface及CreateInstance。這樣當客戶從這種函數得到一個接口
后,它將無需調用AddRef。
(2)在使用完接口之后調用Release。在使用完某個接口之后應調用此接口的Release函數。
(3)在賦值之后調用AddRef。在將一個接口指針賦給另外一個接口指針時,應調用AddRef。換
句話說,在建立接口的另外一個引用之后應增加相應組件的引用計數。
4、在客戶看來,引用計數是處于接口級上而不是組件級上的。
5、為什么選擇為每一個接口單獨維護一個引用計數而不是針對整個組件維護引用計數?(1)使
程序調試更為方便;(2)支持資源的按需獲取。
6、AddRef&Release的例子
ULONG _stdcall AddRef()
{
??? return InterlockedIncrement(&m_cRef);
}
ULONG _stdcall Release()
{
??? if(InterlockedDecrement(&m_cRef)
??? {
??????? delete this;
??????? return 0;
??? }
??? return m_cRef;
}
7、當建立一個新組件時,應建立一個對此組件的引用。因此創建組件時,在將指針返回給客戶
之前,應該增大組件的引用計數值。這使程序員可以不必在調用CreateInstance 或
QueryInterface之后記著去調用AddRef。
8、引用計數規則優化:
(1)輸出參數規則:任何在輸出參數中或作誒返回值返回一個新的接口指針的函數必須對此接
口指針調用AddRef。
(2)輸入參數規則:對傳入函數的接口指針,無需調用AddRef和Release,這是因為函數的生命
期嵌套在調用者的生命周期內。
(3)輸入-輸出函數規則:對于用輸入-輸出參數傳遞進來的接口指針,必須在給它賦另外一個
接口指針之前調用其Release。在函數返回之前,還必須對輸出參數中所保存的接口指針調用
AddRef。如:
void ExchangeForCachedPtr( int i, IX **ppIX)
{
??? (*ppIX)->Fx();? //Do something with in-parameter.
??? (*ppIX)->Release();//Release in parameter.
??? *ppIX = g_Cache[i];//Get cached pointer.
??? (*ppIX)->AddRef();//AddRef pointer.
??? (*ppIX)->Fx();//Do something with out-parameter.
}
(4)局部變量規則:對于局部復制的接口指針,由于它們只是在函數的生命周期內才存在,因
此無需調用AddRef和Release。
(5)全局變量規則:對于保存在全局變量中的接口指針,在將其傳遞給另外一個函數之前,必
須調用其AddRef。由于此變量是全局的,因此任何函數都可以通過調用其Release來終止其生命
期。對于保存在成員變量中的接口指針,也應按此種方式進行處理。因為類中的任何成員函數都
可以改變次中接口指針的狀態。
(6)不能確定時的規則:對于任何不能確定的情形,都應調用AddRef和Release對。
?
////////////////////////////////////////////////////////////////////
第5章 動態鏈接
1、從DLL中輸出函數:用extern "c"標記。
2、在使用VC時,可以用DUMPBIN。EXE來得到某個DLL中所輸出的符號的清單。如下面的命令:
dumpbin -exports Cmpnt1.dll
3、裝載DLL:LoadLibrary以被裝載的DLL的名稱作為參數并返回一個指向所裝載的DLL的句柄。
win32的GetProcAddress函數可以使用此句柄以及待用的函數的名稱,然后返回一個指向次函數
的指針。
4、使用DLL實現組件的原因:DLL可以共享它們所鏈入的應用程序的地址空間。
//////////////////////////////////////////////////////////////////////
第6章 關于HRESULT、GUID、注冊表及其他細節
1、HRESULT值的結構:
?_________________________________________________________
|????|??????????????????????????????????????????????????|????????????????????? ??????????????????????????????????????|???????????????????????????????????????????
|?? ?|?????????????15bits設備代碼? ????????? |?????? 16bits返回代碼?????????????? ??? ???????? |
|__|_________________________|______________________________|
?31 30?????????????????????????????????????????16??15?????????????????????????? 0???????????????????????
2、常用的HRESULT值:
3、一般不能直接將HRESULT值同某個成功代碼(如S_OK)進行比較以決定某個函數是否成功也不
能直接將其同某個失敗代碼(如E_FAIL)進行比較以決定函數調用是否失敗。應該使用
SECCEEDED和FAILED宏。
HRESULT hr = CoCreateInstance(...);
if(FAILED(hr))
??? return ;
hr = pI->QueryInterface(...);
if(SUCCEEDED(hr))
{
??? pIX->Fx();
??? pIX->Release();
}
pI->Release();
4、當前所定義的設備代碼:
——————————————————————————————————
FACILITY_WINDOWS??8
FACILITY_STORAGE??3
FACILITY_SSPI???9
FACILITY_RPC???1
FACILITY_WIN32???7
FACILITY_CONTROL??10
FACILITY_NULL???0
FACILITY_ITF???4
FACILITY_DISPATCH??2
FACILITY_CERT???11
——————————————————————————————————
5、關于定義自己的HRESULT的一些一般性規則:
(1)不要將0X0000及IX01FF范圍內的值作為返回代碼。這些值是為COM所定義的FACILITY_ITF代
碼而保留的。只有遵循這一規則,才不致使用戶自己定義的代碼同COM所定義的代碼相混淆。
(2)不要傳播FACILITY_ITF錯誤代碼。
(3)盡可能使用通用的COM成功及失敗代碼。
(4)避免定義自己的HRESULT,而可以在函數中使用一個輸出參數。
6、用MAKE_HRESULT宏來定義一個HRESULT值,此宏可根據所提供的嚴重級別、設備代碼及返回代
碼生成一個HRESULT值。如:
MAKE_HRESULT(SEVERITY_ERROR,FACILITY_ITF,100);
7、GUID是英文Globally Unique Identifier(全局唯一標識符)的首字母縮寫。IID是一個128比
特(16)字節的一個GUID結構。
8、生成GUID :UUIDGEN.EXE和GUIDGEN.EXE
9、GUID的比較:操作符==;等價函數IsEqualGUID,IsEqualIID,IsEqualCLSID。
10、將GUID作為組件標識符
11、由于一個GUID值占用了16個字節,因此一般不用值傳遞GUID參數。而大量使用的是按引用傳
遞。
12、COM只使用了注冊表的一個分支:HKEY_CLASSES_ROOT。
13、注冊表CLSID是一個具有如下格式的串:
{********-****-****-****-************}
14、CLSID關鍵字的子關鍵字InprocServer32關鍵字的缺省值是組件所在的DLL文件名稱。
15、一些特殊關鍵字:
(1)AppID:此關鍵字下的子關鍵字的作用是將某個APPID(應用程序ID)隱射成某個遠程服務
器名稱。分布式COM將用到此關鍵字。
(2)組見類別:注冊表的這一分支可以將CATID(組件類別ID)映射成某個特定的組件類別。
(3)Interface:用于將IID映射成與某個接口相關的信息。
(4)Licenses:保存授權使用COM組件的一些許可信息。
(5)TypeLib:類型庫關鍵字所保存的是關于接口成員函數所用參數的信息等。
16、ProgID命名約定:
<Program>.<component>.<version>
17、從ProgID到CLSID的轉換:COM庫函數:CLSIDFromProgID和ProgIdFromCLSID:
CLSID clsid;
CLSIDFromProgID("****.****.****",&clsid);
18、自注冊:DLL一定要輸出下邊兩個函數:
STDAPI DllRegisterServer();
STDAPI DllUnregisterServer();
用戶可以使用程序REGSVR32.EXE來注冊某個組件,它實際上是通過上述函數來完成組件的注冊的
。
19、組件類別使開發人員能夠使開發人員無需創建組件實例就能決定它是否特工所需接口。一個
組件類別實際上就是一個接口集合,該集合將被分配給一個GUID,此GUID此時被稱做是CATID。
對于某個組件而言,若它實現了某個組件類別的所有接口,那么它可以將其注冊成該組件類別的
一個成員。這樣,客戶就能夠通過從注冊表中選擇只屬于某個特定組件類別的組件而準確找到它
所需的組件。
20、組件類別的用途:指定某個組件必須實現的接口集合;用于指定組件需要其客戶提供的接口
集合。
22、在使用COM庫中的其他函數(除CoBuildVersion外,此函數將返回COM庫的版本號)之前,進
程必須先調用CoInitialize來初始化COM庫函數。當進程不再需要使用COM庫函數時,必須調用
CoUninitialize。對每一個進程,COM庫函數只需初始化一次。這并不是說不能多次調用
CoInitailize,但需保證每一個CoInitialize都有一個相應的CoUnoinitialize調用。當進程已
經調用過CoInitialize后,再次調用此函數所得到的返回值將是S_FALSE而不再是S_OK.
23、OLE是建立在COM基礎之上的,它增加了對類型庫、剪貼板、拖放、ActiveX文檔、自動化以
及ActiveX控件的支持。在OLE庫中包含對這些特性的額外的支持。在需要使用這些特性時,應調
用OleInitailize及OleUninitialize,而不是CoInitailize和 CoUninitialize。Ole*函數將調用
Co*函數。但若程序中沒有用到那些額外的功能,使用Ole*將會造成資源的浪費。
24、COM中分配和釋放內存的標準方法:任務內存分配器。使用此分配器,組件可以給客戶提供
一塊可以由客戶刪除的內存。可在多線程應用程序中使用。
一些方便的函數:
void? *CoTaskMemAlloc(
ULONG cb? //size in bytes of block to be allocated
);
void CoTaskMemFree(
void *pv? //pointer to memory block to be freed
);
25、StringFromGUID2可以將某個GUID轉換成一個字符串:
wchar_t szCLSID[39];
int r = ::StringFromGUID2(CLSID_Component1,szCLSID,39);
?傳給StringFromGUID2的參數是一個Unicode串(即一個寬字符wchar_t類型的數組而不是char類
型的字符數組)。在非Unicode的系統中,需要將結果轉化為單字節字符(char)。為此,可以使
用ANSI的wcstombs函數如下:
#ifndef _UNICODE
char szCLSID_single[39];
wcstombs(szCLSID_single,szCLSID,39);
#end if
26、
----------------------------------------------------------------------------
函數????????????????? 用途
-----------------------------------------------------------------------------
StringFromCLSID?????? 將CLSID轉化成文本串
-----------------------------------------------------------------------------
StringFromIID???????? 將IID轉化成文本串
------------------------------------------------------------------------------
StringFromGUID2?????? 將GUID轉化成文本串。此串將被存放在調用者所分配的緩沖區中
------------------------------------------------------------------------------
CLSIDFromString?????? 將一個文本串轉化成CLSID
------------------------------------------------------------------------------
IIDFromString???????? 將一個文本串轉化成IID
------------------------------------------------------------------------------
posted on 2007-03-22 15:41
jay 閱讀(648)
評論(0) 編輯 收藏 引用 所屬分類:
COM