在此文章記錄一下 COM 的簡單介紹,包括 COM 相關技術要點以及程式設計的過程,友善以後查閱。
目錄
1、概述
2、COM結構
2.1、COM對象
2.2、COM接口
2.3、IUnknown接口
2.3.1、引用計數
2.3.2、接口查詢 QueryInterfqace
2.4、接口描述語言IDL
2.5、COM對象和接口圖示法
3、COM實作
3.1、程序内元件和程序外元件
3.2、通過系統資料庫管理COM對象
3.3、類廠
3.3.1、類廠的定義
3.3.2、類廠的使用
3.4、COM庫
3.5、COM實作過程
3.5.1、程序内元件與客戶的協作過程
1、概述
COM,Component Object Model,即元件對象模型,是一種以元件為釋出單元的對象模型,這種模型使各軟體元件可以用一種統一的方式進行互動。COM 既提供了元件之間進行互動的規範,也提供了實作互動的環境,因為元件對象之間互動的規範不依賴于任何特定的語言,是以,COM也可以是不同語言協作開發的一種标準。
COM實際上是一種元件标準,COM不僅僅提供了元件之間的接口标準,它還引入了面向對象的思維。在COM标準中,對象是一個非常活躍的元素,常常被稱為COM對象。元件子產品為COM對象提供了活動的空間,COM對象以接口的方式提供服務,這種接口就被稱為COM接口。COM元件、COM對象、COM接口三者的關系如下圖所示:

在Windows系統平台上,一個COM元件可以是一個DLL(Dynamic Linking Library,動态連結庫)檔案,也可以是一個EXE(可執行程式)檔案。一個元件程式可以包含多個COM對象,并且每個COM對象可以實作多個接口。
當另外的元件或者普通程式(即元件的客戶程式)調用元件的功能時,它首先建立一個COM對象或者通過其他途徑獲得COM對象,然後通過該對象所實作的COM接口調用它所提供的服務。當所有的服務結束之後,如果客戶程式不再需要該COM對象,那麼它就應該釋放掉對象所占用的資源,包括對象自身。
2、COM結構
2.1、COM對象
COM是面向對象的軟體模型,因而對象是它的基本要素之一,那麼COM對象是什麼呢?類似于C++中對象的概念,對象是某個類(Class)的一個執行個體;而類則是一組相關的資料和功能組合在一起的一個定義。使用對象的應用(或另一個對象)被稱為客戶,有時也可稱為對象的使用者。
在COM中,每個對象用一個128位的GUID來辨別,被稱為CLSID(Class Indentifier, 類辨別符或類ID)。定義的格式如下所示:
這個GUID的值可以通過編譯器的工具進行自動生成。
2.2、COM接口
接口是一組邏輯上相關的函數集合,其函數也被稱為接口成員函數。按照習慣,接口名常以"I"為字首,比如下一章的 IUnknown 接口。對象通過接口成員函數為客戶提供各種形式的服務。
在COM模型中,對象本身對于客戶是不可見的,客戶請求服務時,隻能通過接口進行。那客戶是怎麼擷取指定的接口呢?與對象類似,接口采用的仍然是128位的 GUID(Globally Unique Identifier, 全局唯一辨別符),隻不過名稱不同。客戶通過GUID擷取接口指針,再通過接口指針,客戶就可以調用其相應的成員函數了。至于具體功能如何實作,則完全有對象的接口内部實作。是以,在COM模型中,對象通過接口及接口中的函數為客戶提供服務,對于客戶來說,它隻與接口打交道。一般來說,接口是不變的,隻要客戶期望的接口在元件對象中還存在,它就可以繼續使用該接口提供的服務。對象可以支援多個接口,是以對元件對象的更新可以通過增加接口的辦法來實作,這樣得到的新接口可以不影響老接口的使用。新客戶可使用新增的接口,老使用者可以在不更新代碼的情況下繼續使用老接口。
COM接口和經常說的API有點相似。通過API接口層,可以很好地把兩個程式連接配接起來,但存在一些問題:1)、當API非常多時,使用會非常不友善,需要對函數進行組織。2)、API函數需要标準化,按照統一的調用方式進行處理,以适應不同程式設計語言的實作,包括參數傳遞順序、參數類型、函數傳回處理都需要标準化。而COM定義了一套完整的接口規範,不僅可以彌補以上API作為元件接口的補足,還充分發揮了元件對象的優勢,并實作了元件對象的多态性。
從技術上講,接口是包含了一組函數的資料結構,通過這組資料結構,客戶代碼可以調用元件對象的功能。客戶程式用一個指向接口的資料結構指針來調用接口成員函數。調用過程和C++中的虛函數的調用很相似。接口的結構如下所示:
如果是在C++中,接口的定義就是一個包含了一組純虛函數的抽象類。接下來會使用C++作為開發語言完成COM的程式設計執行個體。
以字典元件為例,字典元件的繼承樹如下所示:
IDictionary的字典接口,提供了插入單詞、删除單詞等功能,則接口的定義以及記憶體機構如下所示:
那麼對象和接口之間到底有什麼關系呢?為了友善了解,以C++舉例,對象實際上是實作了接口類的實作類的一個執行個體。比如,有一個類CDictonary,它實作了接口IDictionary,則對象就是類CDictonary的一個執行個體。
,則接口 IDictionary和CDictonary對象的記憶體結構如下所示:
是不是和C++中抽象類的用法是一樣的。
2.3、IUnknown接口
COM規定,每一個接口都必須從IUnknown接口繼承過來,那是因為在 IUnknown接口提供了兩個非常重要的特性:生存周期控制和接口查詢,IUnknown引入了“引用計數”(reference counting)的方法,可以有效的控制對象的生存周期。IUnknown使用了“接口查詢”(QueryInterfqace)的方法提供接口查詢的功能,可以完成接口之間的跳轉。
IUnknown接口的C++定義如下:
IUnknown包含了三個成員函數。函數 QueryInterfqace用于查詢COM對象的其他接口指針,函數 AddRef和Release用于對引用計數進行操作。是以,在COM中上述的 IDictionary接口需要定義成如下形式:
IDictionary除了定義了自己的接口函數,還從IUnknown中繼承了上述的三個接口函數。
2.3.1、引用計數
通常比較合理的方式是采用對象一級的引用計數以便控制對象的元件的生存周期。如果是在對象一級實作引用計數,可以使用C++類的成員變量來實作。以前面的類CDictonary和接口IDictionary為例,在CDictonary類定義了一個私有成員變量 m_Ref用于引用計數。實作引用計數的方式如下所示:
2.3.2、接口查詢 QueryInterfqace
對于接口查詢函數 QueryInterfqace,其中輸入參數 iid 為接口辨別符,輸出參數 ppv 為查詢得到的結果的接口指針,如果對象沒有實作 iid 所辨別的接口,則輸出參數 ppv 指向空(NULL)。傳回值 HRESULT 是一個32位的整數,具有以下三種結果:
- S_OK:查到了指定的接口,接口指針存放在 ppv 輸出參數中;
- E_NOINTERFACE:對象不支援所指定的接口,*ppv為NULL;
- E_UNEXPECTED:發生了以外錯誤,*ppv為NULL;
使用方式參考如下:
實作方式參考如下:
上圖中,當iid == IID_IUnknown時,并沒有轉成為IUnknown類型,是因為,根據上文中繼承樹的概念,如果傳回的是 IUnknown 類型的指針是存在二義性的,因為 CDictionary繼承了兩個接口,而這兩個接口又都繼承自IUnknown,是以為了避免出現二義性,則直接轉換為IDictionary類型。
2.4、接口描述語言IDL
IDL,Interface Description Language,接口描述語言。IDL提供了一種不依賴于任何語言的接口描述方法,是以,它可以成語元件程式和客戶程式之間的共同語言。
COM規範使用的IDL接口描述語言不僅可用于定義COM接口,同時還定義了一些常用的資料類型,也可以描述自定義的資料結構,對于接口成員函數,我們可以指定每個參數的類型、輸入輸出特性、甚至支援可變長度的數組的描述。IDL支援指針類型,與C/C++很類似。例如上面的 IDictionary接口用IDL描述如下所示:
,可以通過MIDL工具,将IDL接口描述檔案編譯成C/C++相容的接口描述頭檔案(.h)。
2.5、COM對象和接口圖示法
COM對象和接口有一種特殊的圖形描述方法。CDictonary的對象和接口圖形化描述如下圖所示:
3、COM實作
3.1、程序内元件和程序外元件
程序内元件:如果用動态連結庫 DLL的方式實作元件程式,則客戶程式調用元件程式的服務時,會把元件程式裝入到自己的程序中,是以客戶程式群組件程式是運作在同一個程序中,就把這種元件程式稱為程序内元件。
程序外元件:實作元件程式的另一種方式是EXE程式,這種元件程式在被調用時有其自己的程序空間,是以客戶程式群組件程式運作在不同的程序空間中,這種元件程式就被稱為程序外元件。
本文隻考慮程序内元件。
3.2、通過系統資料庫管理COM對象
在前面第2章中已經提到,COM規範使用128位的GUID來辨別COM對象和接口,客戶程式通過這些GUID來建立COM對象并與對象進行互動。按照COM規範,客戶程式通過COM庫完成對象的建立工作(将在3.4章節講述)。COM庫通過系統系統資料庫(system registry)所提供的資訊進行元件的建立工作。
元件程式把它所實作的COM對象資訊以及接口資訊都儲存到系統資料庫中,這個過程被稱為元件注冊,如果元件程式具有這種自注冊能力則稱該元件程式為自注冊元件。客戶程式在建立元件對象時,也需要直接或間接地通路系統資料庫中的資訊。通常,元件對象的建立工作由COM庫來完成,COM庫所進行的很多操作都要依賴系統系統資料庫提供的資訊才能完成。
程序内元件的自注冊過程:對于程序内元件,因為它隻是一個動态連結庫,本身不能直接運作,是以必須被某個程序調用才能獲得控制。Windows提供了一個用于程序内元件的實用工具RegSvr32.exe,隻要程序内元件提供了相應的入口函數,則RegSvr32就可以完成注冊或登出工作。元件程式的兩個用于注冊的入口函數為 DllRegisterServer 和 DllUnregisterServer 。
RegSvr32程式本身并不進行注冊工作。是以當用下面的指令行方式運作 RegSvr32時【RegSvr32 c:\**.dll】,RegSvr32調用元件程式 **.dll 中的 DllRegisterServer 函數完成元件程式的注冊工作;當用下面的指令行方式運作 RegSvr32時【RegSvr32 /u c:\**.dll】,RegSvr32調用元件程式 **.dll 中的的 DllUnregisterServer 函數完成元件程式的登出工作。是以,程序内元件實作自注冊的工作就變成了提供兩個引出函數 DllRegisterServer 函數和 DllUnregisterServer 函數,在函數體内完成元件對象的資訊的注冊或登出操作。
3.3、類廠
在建立元件對象時,客戶程式調用COM庫中的函數進行元件對象的建立工作,COM庫的建立函數根據系統資料庫的資訊并調用元件程式的入口函數來建立元件對象。是以元件程式需要提供一個标準的入口函數 DllGetObjectClass 函數,用于提供本元件的元件資訊。而在 DllGetObjectClass 中,是以類廠的方式擷取元件對象的。
3.3.1、類廠的定義
類廠,顧名思義,就是COM類的工廠。如果對C++比較熟悉的話,應該會知道設計模式中的工廠設計模式,其實這個類廠的概念就和工廠設計模式很相似。确切的說,類廠應該成為“對象廠”,因為類廠是COM對象的生産基地,COM庫通過類廠建立COM對象;COM規定,每一個COM類,對應的都要有一個類廠專門用于該COM類的對象的建立工作。
類廠本身也是COM對象,它支援一個特殊的接口 IClassFactory,前面已經講過,在COM中,所有的接口都要繼承IUnknown接口,是以 IClassFactory接口定義如下:
通過接口成員函數 CreateInstance 建立對應的COM對象。因為每個類廠隻隻對特定的COM類對象,是以 CreateInstance 函數知道該建立什麼樣的COM對象。CreateInstance 函數的參數含義如下:
- pUnknownOuter:用于對象類被聚合的情形,一般設定為NULL
- iid:為對象建立完成後客戶應該得到的初始接口IID
- ppv:存放傳回的接口指針
IClassFactory 的另一個成員函數 LockServer用于控制元件的生存周期,在此不多做描述。
如果一個元件程式實作了多個COM對象類,則相應的有多個類廠。是以,上述關于字典元件的結構、和多個類廠的結構就如下所示:
對于多個類廠的情況,可以設計一個比較通用的類廠代碼,把不同的對象類的資訊放到一個資料結構中,當客戶程式請求建立某個CLSID的類廠時,可以選擇對應的資訊結構,然後傳回類廠結構指針。
3.3.2、類廠的使用
因為類廠本身也是個COM對象,它被用于其它COM對象的建立過程,那麼類廠對象又由誰來建立呢?就是前文中提到的 DllGetObjectClass 引出函數!DllGetObjectClass 函數原型如下所示:
參數含義如下:
- clsid:待建立對象的CLSID,因為一個元件可能實作了多個COM對象類,是以必須指定
- iid:接口的IID(GUID)
- ppv:存放類廠接口指針
COM庫在接到對象建立指令後,它要調用程序内元件的 DllGetObjectClass 函數建立類廠對象,并傳回類廠對象的接口指針,COM庫或者客戶一旦有了類廠的接口指針,他們就可以通過類廠接口 IClassFactory 的成員函數 CreateInstance 建立相應的COM對象。
3.4、COM庫
COM除了定義了元件程式和客戶程式互動的規範以外,它也提供了COM的實作部分即COM庫,使得這些規範能夠真正地應用起來。并且COM庫也充當了元件程式和客戶程式之間的橋梁,尤其是在元件對象的建立過程中,以及在對象管理、記憶體管理和一些标準化操作方面起着重要的作用。
COM庫的一些常用函數:
客戶程式調用COM庫建立元件對象的順序圖:
3.5、COM實作過程
整體介紹一下COM客戶程式、COM庫、COM元件程式三者之間的協作過程,算是對前面講述内容的一個概括描述。
3.5.1、程序内元件與客戶的協作過程
下面就以前面介紹的字典元件為例,簡單概括一下協作的過程。