1、COM對象的理解
COM對象類似於C++語言中類的概念,類的每個執行個體代表一個COM對象,它也包括屬性(即狀態)和方法(即操作),狀態反映對象的存在,方法就是介面。
2、COM對象的標識-CLSID
GUID是一個128位的隨機數,重複機率極低。它的值來源於兩部分:空間值(網卡地址或隨機數)和時間值。
獲得GUID值可以使用VC++提供的工具:GUIDGen.exe 和 UUIDGen.exe。或者使用COM庫的API函數CoCreatGuid()。
3、COM對象與C++對象的比較
COM對象將資料完全封裝在對象的內部。C++對象的封裝是在語義上的封裝,通過不同的資料類型實現資料的封裝。
COM對象的可重用性通過包容和彙總實現。C++對象的可重用性通過類的繼承來實現。
COM對象的多態性通過其介面體現,C++對象的多態性通過其虛函數體現。
4、COM介面的作用和意義
COM規範的核心內容是關於介面的定義,雖然COM本身並不複雜,但是圍繞COM介面有很多內容值得仔細探討,包括介面的標識、介面函數的調用習慣、參數處理、介面與對象的關係以及介面與C/C++的關係、COM介面多具有的特性等。
COM定義了一套完整的介面規範,不僅可以彌補API作為組件介面的不足,還從分發揮了組件對象的優勢,並實現了組件對象的多態性。
5、介面定義和標識
從技術上講,介面是包含了一組函數的資料結構,通過這組資料結構,客戶代碼可以調用組件對象的功能。
客戶程式用一個指向介面函數結構的指標來調用介面成員函數。實際上介面指標指向另一個指標pVtable。
介面函數表稱為虛函數表(Virtual Function Table ,簡稱vtable),指向vtable的指標為pVtable。對於一個介面來說,它的虛函數表vtable是確定的。
6、介面設計的問題
在介面成員函數中,字串變數必須用Unicode字元指標,這是COM規範的要求。
COM API函數使用大多數語言慣用的_stdcall調用習慣。
用C語言定義COM介面,需要有結構體struct定義其介面結構,介面成員函數必須有一個this指標。
用C++語言定義COM介面,因為由C++語言class的實現機理可以看出,COM介面結構中的vtable與class的vtable(類的虛函數表)完全一致,因此,用class描述COM介面是最方便的手段。此時,介面成員函數隱藏了this指標。
class 型介面的說明要比struct 型介面的說明簡捷得多。
7、COM介面與對象的聯絡
介面類只是一種描述,並不提供具體的實現過程。如果COM對象要實現介面,則COM對象必須以某種方式把它自身與介面類聯絡起來,然後把介面類的指標暴露給客戶程式,於是客戶程式就可以調用對象的介面功能了。
用 class型介面通過把介面指標(this)與對象資料繫結在一起的方法實現對COM介面的支援比較直觀、簡捷易於理解。實際上,也可以採用其他的方法來實現介面,只要介面成員函數中this指標(即介面指標)與對象資料能建立確定的串連,在介面成員函數中可以訪問到對象資料即可。例如,VC++的MFC 庫和ATL(active template library ,Active Template Library)模板庫分別採用了不同的機制來提供對COM介面的支援。
8、介面描述語言IDL
COM 規範在採用OSF的DCE規範描述遠程調用介面IDL(interface description language ,介面描述語言)的基礎上,進行擴充形成了COM介面的描述語言。介面描述語言提供了一種不依賴於任何語言的介面描述方法,因此,它可以成為組件程式和客戶程式之間的共同語言。
COM 規範使用的IDL介面描述語言不僅可用於定義COM介面,同時還定義了一些常用的資料類型,也可以描述自訂的資料結構,對於介面成員函數,我們可以制定每個參數的類型、輸入輸出特性,甚至支援可變長度的數組的描述。VC++提供了MIDL工具,可以把IDL介面描述檔案編譯成C/C++相容的介面描述標頭檔(.h)。
9、介面的記憶體模型
COM對象往往有自己的屬性資料,它們反映對象的狀態,並用於區分不同的對象。對於有多個對象的客戶,資料屬性是不能公用的。
10、介面的特點
二進位特性
介面不變性
繼承性(擴充性):類似於C++中類的繼承性,介面也可以繼承發展,但方式不同。類繼承不僅是說明繼承,也是實現繼承,即衍生類別可以繼承基類的函數實現,而介面繼承只是說明繼承,即派生的介面只繼承了基底介面的成員函數說明,而沒有繼承基底介面的實現。類繼承允許多重繼承,但介面繼承只允許單繼承。根據COM規範,所有介面都必須從IUnknown派生,可以直接派生,也可以間接派生。但大多數都是直接派生。OLE系統中,介面最後字母是“2”或“Ex”的,標煤它是一個繼承介面。
多態性:COM對象具有多態性,其通過COM介面體現。
11、IUnknown介面提供了兩個非常重要的特性:生存期控制(使用引用計數)和介面查詢。
12、IUnknown介面引用計數的設定層級
引用計數在組件一級實現則計數解析度太粗(選擇全域變數),在對象一級實現恰好(使用C++類的成員變數),在介面一級實現則計數解析度太細(使用類成員變數)。
13、使用引用計數的規則
根據不同場合使用或者傳遞介面指標標量進行分類,並給出相應的規則:
(1)函數的參數中使用介面指標變數。
輸入參數:由於輸入參數由調用函數控制,因此被調用函數執行過程中,介面指標一定保持有效,引用計數不需要改變。
輸出參數:輸出參數是指在被調用函數執行過程中進行賦值的參數,而且被調用函數並沒有用到函數初始化傳進來的值,輸出參數相當於函數的一個傳回值。在C/C++語言中,輸出參數為一個指標變數(COM中不使用引用變數)。因為輸出參數相當於在被調用函數中產生了一個新的介面指標變數,因此,在被調用函數返回之前,對輸出參數應該調用AddRef使介面引用計數增1。這條規則也適用於函數傳回值為介面指標變數的情況。
輸入-輸出參數:在參數被修改之前,對原來傳進來的介面指標調用Release以使引用計數減1,在參數被修改之後,對新的介面指標變數調用AddRef,以標記對新的介面指標的引用。如果在函數執行過程中參數沒有被修改,則不需要改變。
(2)局部介面指標變數:因為在局部函數塊中,介面指標總是有效,所以,一個局部介面指標變數被賦了值並調用了介面成員函數,引用計數不需要改變。
(3)全域介面指標變數:把全域介面指標變數作為輸入參數傳給某個函數之前,應該調用AddRef以保證在函數調用中可以使用給介面指標變數,因為它是全域變數,其他的函數有可能會調用Release函數。在函數返回之後應該調用Release函數。
(4)C++中類成員變數為介面指標變數:因為對於類的範圍來講,成員變數相當於全域變數,因此適用於規則(3)。
(5)當以上情形都不適合時,使用以下一般的規則:
在順序執行過程中,如果要對一個介面指標變數賦值,則對賦值後的介面指標變數調用AddRef,並且,如果賦值前的介面指標變數還沒有結束,則賦值前必須對它調用Release以便先結束它的使用。
如果要結束使用一個介面指標變數,以後不再用到它了,則調用Release函數。
14、介面查詢
使用QueryInterface函數查詢介面,其傳回值有S_OK、E_NOINTERFACE、E_UNEXPECTED。
15、COM對象的介面原則
(1)對於同一個對象的不同介面指標,查詢得到的IUnknown介面必須完全相同。即每個對象的IUnknown介面指標是唯一的。
(2)介面對稱性。即對一個介面查詢其自身總應該成功。
(3)介面自反性。
(4)介面傳遞性。
(5)介面查詢時間無關性。
16、多介面COM對象的實現方法
在C++語言中有兩種實現方法:一是使用多重繼承,把所支援的介面作為其基類,然後在對象類中實現介面成員函數;二是使用內嵌介面類成員。