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 ,活動模板庫)模板庫分別采用了不同的機制來提供對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++語言中有兩種實現方法:一是使用多重繼承,把所支持的接口作為其基類,然後在對象類中實現接口成員函數;二是使用內嵌接口類成員。