程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> COM原理與應用----COM的實現

COM原理與應用----COM的實現

編輯:關於C++

1、COM的實現與操作系統平台密切相關

因為COM最初源於Microsoft Windows平台,所以COM實現部分(即COM庫)很多地方直接用到了Windows系統的一些特性,比如系統注冊表、動態連接庫等等,但實際上 COM是一個與平台無關的組件軟件模型。Windows上使用的COM標准只是COM的一個具體實現。

2、COM的實現方法

進程內組件(DLL ,in-process component)。

進程外組件(EXE ,out –of-process component)。

3、DLL程序的創建方法

(1)創建一個DLL工程

(2)創建DLL時,應該使用_stdcall調用習慣引出函數,並使用extern “C”說明符。這樣能夠保證與其他編譯器和編程語言的兼容。

(3)按照傳統的編程方法,編寫一個DEF文件,用來描述DLL程序的模塊信息,即列出所有引出函數,並給每個引出函數分配一個唯一的序號。在Win32平台上,可以不使用DEF文件,而直接在函數說明時使用_declspec(dllexport)說明符,如下:

extern “C” _declspec(dllexport) int _stdcall MyFunction();

4、客戶程序操作DLL程序的三個系統函數

LoadLibrary,裝載DLL模塊函數

GetProcAddess,取引出函數地址的函數

FreeLibrary,釋放DLL程序的函數

5、DLL的三點說明

(1)對於進程內組件,因為客戶程序與DLL程序在同一個地址空間,所以,DLL程序不僅可以引出函數,也可以引出全局變量。

(2)VC++提供了使用工具DumpBin,通過/EXPORTS選項可以列出DLL程序中的所有被引出的信息。(實際運行中需要LINK.EXE和MSPDB60.DLL的支持)

(3)如果客戶程序本身也是一個DLL程序,,則它一定要先被裝入到進程空間中。

6、進程外組件與客戶通信跨躍進程邊界協同工作

(1)兩個問題

一個進程如何調用另一個進程中的函數。

參數如何從一個進程被傳遞到另一個進程中。

(2)Windows平台上不同進程間通信的方法

動態數據交換(DDE)

命名管道(named pipe)

共享內存

(3)COM采用的進程間通信的方法

本地過程調用(LPC,local procedure call),用在同一機器的不同進程間的通信。

遠過程調用(RPC),用於不同機器間的進程間通信。

跨進程通信是操作系統實現的重要部分,而且,在系統底層實現跨進程操作更為便利,因為在系統一級,它可以控制應用進程的資源分配,包括邏輯內存空間到物理內存的映射、CPU時間的調度等。從控制能力來講,操作系統可以調用任何一個進程中的函數。

(4)應用程序調用其他進程中系統服務的過程

上圖的調用過程中涉及到跨進程操作,實際上也就是用到LPC。應用A實際上調用的是系統模塊DLL,其稱為存根(stub)模塊。

(5)客戶程序和進程外組件之間的調用關系

進程外組件較進程內組件的效率要低,但在跨進程的調用中也為客戶程序帶來了安全性。在代理DLL和存根DLL之間的通信是通過列集和散集(對參數和返回值進行翻譯和傳遞)操作來完成的。

如果只考慮客戶程序和組件程序,那麼事情就簡化的多了,客戶程序調用接口成員函數就好像是直接進行的,如上圖虛線所示。按照這樣簡化的結構,進程外組件和進程內組件就有了一致的模型了,COM正是通過這種方式實現進程模型的透明特性的。如果組件程序運行在不同的機器上,則代理DLL和存根DLL就通過RPC方式進行網絡上的過程調用,從而實現分布式組件對象模型,所有這些特性的實現可以完全建立在操作系統提供的LPC和RPC模塊基礎上,而不需要應用系統開發人員去考慮這些底層的細節技術問題。

(6)進程外組件的實現

除了實現組件程序外,還應給實現代理DLL和存根DLL兩個程序模塊,它們只與 COM接口有關,只負責接口成員函數調用過程中的中間處理工作。如果使用自定義的COM接口,則應該建立自己的DLL程序;如果使用COM預定義的標准接口或者OLE接口,則可以直接使用系統提供的DLL,COM庫會為我們處理這些細節。

7、通過注冊表管理COM對象

通常,組件對象的創建工作由COM來完成,COM庫通過系統注冊表(systrm registry)所提供的信息進行組件的創建工作。

8、COM組件注冊信息

注冊表結構與COM庫是直接相關的,實現COM庫時必須同時定義出注冊表的結構。在Windows平台上,COM庫所要求的注冊信息都被放到其注冊表中。RegEdit.exe可以用來編輯注冊表。

COM組件信息在HKEY_CLASSES_ROOT的CLSID下。若是進程內組件,則組件的CLSID子鍵下包含了InprocServer32子鍵;若是進程外組件,則組件的CLSID子鍵下包含了LocalServer32子鍵,它們的缺省值為組件程序的全路徑文件名。與CLSID同一層上,Interface子鍵給出了當前系統中一些COM接口的配置信息,TypeLib子鍵給出了當前系統中類型庫的信息。代理DLL和存根DLL的信息分別保存在CLSID下的ProxyStubClsid或ProxyStubClsid32子鍵下。CLSID下的ProgID(program identifier ,程序標識符)子鍵記錄了組件對象的類標識符,COM提供了兩個API函數CLSIDFromProgID和ProgIDFromCLSID,用於在 128位正數值和類標識符之間轉換。

COM提供了在注冊表中對COM組件進行分類的機制,分類的原理很簡單,如果COM組件支持同樣的一組接口,則可以把它們分到統一類中,一個組件對象可以被分到多個類中。

類別信息也用一個GUID來描述,稱為CATID。類別特性只是COM對象的部分特性,如果COM對象要加入到某個類別中,則它必須實現該類別指定的多有接口。在HKEY_CLASSES_ROOT鍵下有一個子鍵“Component Gategories”,包含了當前機器上所有的組件類別,其中列出了每個組件類別的CATID。用VC++提供的OleView.exe工具可以看到類別信息。

9、COM組件的注冊操作

當組件程序被安裝到機器上之後,必須通過注冊才能使客戶程序通過注冊表使用它。進程內組件不能直接運行,所以必須通過其他進程調用才能獲得控制,而進程外組件可以直接運行,可以在執行過程中完成自身的注冊操作。Windows系統工具RegSer32.exe,可用於注冊進程內組件,大需要進程內組件提供相應的入口函數DllRegisterServer和DllUnregisterServer。

10、客戶程序如何使用組件程序

客戶程序並不像第二章中那樣直接調用組件程序的引出函數CreateObject,而是調用COM庫的函數進行組件對象的創建工作,COM庫的創建函數根據注冊表的信息並調用組件程序的入口函數來創建組件對象。組件程序需要提供一個標准的入口函數DllGetObjectClass,用於提供本組件程序的組件信息。

11、類廠

類廠可稱為“對象廠”,因為類廠是COM對象的生產基地,COM庫通過類廠創建COM對象。對應每一個COM類,有一個類廠專門用於該COM類的對象創建操作。類廠本身也是一個COM對象,它支持一個特殊的接口:IClassFactory。

接口IClassFactory的成員函數CreateInstance用於創建對應的COM對象,LockServer用於控制組件的生存周期。

12、類廠的使用

因為類廠本身也是一個COM對象,用於其他COM對象的創建過程。它由引出函數DllGetObjectClass創建,DllGetObjectClass函數並不是COM庫函數,而是由組件程序實現的引出函數。

COM庫在接到對象創建的指令後,它要調用進程內組件的DllGetObjectClass函數,由該函數創建類廠對象,並返回類廠對象的接口指針,COM庫或者客戶一旦有了類廠的接口指針,它們就可以通過類廠接口IClassFactory的成員函數CreateInstance創建相應的 COM對象。

13、COM庫與類廠的交互

在COM庫中,有三個API函數可用於對象的創建,它們分別是CoGetClassObject、CoCreateInstance和 CoCreateInstanceEx。通常情況下,客戶程序調用其中之一完成對象的創建,並返回對象的初始化接口指針。COM庫與類廠也通過這三個函數進行交互。

COM對象是進程內組件對象:CoGetClassObject調用DLL模塊的DllGetClassObject引出函數,把參數clsid、iid 和ppv傳給DllGetClassObject函數,由DllGetClassObject創建類廠,並返回類廠對象接口指針。

COM對象是進程外組件對象:情形復雜得多。首先CoGetClassObject函數啟動組件進程,然後一直等待,直到組件進程把它支持的COM類對象的類廠注冊到COM中,於是,CoGetClassObject函數把COM中相應的類廠信息返回。因此,組件外進程被COM庫啟動時(帶命令行參數 “/Embedding”),它必須把所支持的COM類的類廠對象通過CoRegisterClassObject函數注冊到COM中,以便COM庫創建 COM對象使用。當進程退出時,必須調用CoRevokeClassObject函數以便通知COM它所注冊的類廠對象不再有效。組件程序調用 CoRegisterClassObject函數和CoRevokeClassObject函數必須配對,以保證COM信息的一致性。

關於三個創建對象的函數的選擇:

(1)如果創建遠程對象或者希望一次獲得對象的多個接口指針,則選用CoCreateInstanceEx函數。

(2)如果希望獲取類廠對象或者要調用類廠的某些成員函數,則選用CoGetClassObject函數,以便獲得類廠對象,並對類廠對象進行操作。

(3)在其他情況下,使用CoCreateInstance函數創建對象,這是最常用的方法。

14、CoGetClassObject與組件程序的交互過程的例子

用以說明在COM對象創建過程中,客戶程序、COM庫和進程內組件程序三者之間的順序關系。

15、類廠對組件生存期的控制

類廠是一個COM對象,但通常只把它當作創建其他組件對象的手段,一般情況下,客戶程序或者COM庫只是在創建組件對象的時候才使用類廠對象的接口指針,創建完成後就把類廠對象丟棄掉。如果用戶希望保留類廠的接口指針繼續使用,則在類廠中引入鎖計數來控制組件程序的生存周期。

16、COM庫

COM庫在整個COM對象體系中起了很重要的作用。COM除了定義了組件程序和客戶程序交互的規范以外,它也提供了COM的實現部分即COM庫,使得這些規范能夠被真正地應用起來。並且,COM庫也充當了組件程序和客戶程序之間的橋梁,尤其是在組件對象的創建過程中,以及在對象管理、內存管理和一些標准化操作等方面起著重要的作用。

在客戶程序和組件程序建立起協作關系之前,它們之間的通信只能靠COM庫來傳遞,並且組件程序和客戶程序都可能要用到COM庫提供的各種服務。

17、COM庫的初始化與卸載

COM庫的初始化函數是CoInitialize,其參數pMalloc用於指定一個內存分配器,它是一個IMalloc指針接口,可由應用程序指定內存分配原則。一般將pMalloc設為NULL,由COM庫將使用缺省提供的內存分配器。

一個進程對COM庫只需要(也必須)進行一次初始化。在成功初始化並使用完COM庫後,必須調用COM庫的終止函數CoUninitialize。但有一個函數的調用不需要COM庫的初始化與卸載,就是CoBuildVersion,該函數用於獲取COM庫版本。

18、COM庫的內存管理

(1)內存使用的兩種情況:一種是在客戶程序和組件程序建立協作關系之前,靠COM庫進行通信,COM需要申請內存,內存不一定是COM庫本身使用,也可能是它替組件程序申請並提交出來的;另一種情況是,因為客戶程序通過接口指針對組件程序進行操作,雖然這種調用可能是直接進行的,但組件程序申請的內存有可能由客戶程序來釋放,所以,在COM庫和組件程序、客戶程序之間需要有統一的內存管理辦法。

(2)COM庫不僅提供了這樣的內存管理器,還提供了內存管理器的標准,應用程序可以按照COM規范指定的標准建立自定義內存管理器,以取代COM庫的缺省內存管理器。

(3)在COM庫初始化成功之後,不管是使用缺省內存管理器還是使用自定義的內存管理器,應用程序都可以使用COM庫進行內存分配或釋放,為此,COM庫提供了兩種操作方法:

A、直接使用IMalloc接口指針。在客戶程序或者組件程序中調用COM庫函數CoGetMalloc,通過IMalloc接口指針和CoGetMalloc函數實現內存管理的統一。

B、COM庫封裝了三個API函數,可用於內存分配和釋放:CoTaskMemAlloc、CoTaskMemRealloc、 CoTaskMemFree。這三個函數分別對應於IMalloc的三個成員函數:Alloc、Realloc和Free,參數的定義也完全一致。

(4)COM庫的兩種內存管理器:CoGetMalloc函數可用來獲取COM庫的內存管理器。一種是在初始化時指定的內存管理器或者其內部缺省的管理器(即作業管理器,task allocator),這種管理器在本進程內有效;另一種是跨進程的共享分配器,由OLE系統提供,它可在一個進程內分配內存並傳給第二個進程,在第二個進程內使用此內存甚至釋放掉此內存。

19、組件程序的裝載和卸載

COM庫對組件程序的裝載和卸載進行控制。客戶程序是在運行時刻與組件程序建立連接的,而且,一旦連接起來以後,客戶程序和組件程序的通信是直接進行的,並不需要COM庫的參與,但組件程序的裝載是在客戶創建第一個組件對象時進行的,組件程序的卸載是在最後一個組件對象被釋放之後進行的,這兩個動作並不由客戶程序直接完成,而是在COM庫中完成的。

進程內組件的卸載滿足的兩個條件:組件中對象數為0,類廠的鎖計數器為0。客戶程序調用CoFreeUnusedLibraries函數完成卸載工作。

進程外組件的卸載比較簡單,因為組件程序運行在單獨的進程中,一旦退出的條件滿足,它只要從進程額主控函數返回即可。在Windows系統中,進程的主控函數為WinMain。類廠對象的引用計數無法控制進程的生存期,所以引入類廠對象的加鎖和減鎖操作。

20、COM庫的常用函數

在COM庫中還給出了許多標准接口的定義,例如IUnknown、IClassFactory、IMalloc。

21、HRESULT類型

在COM中大多數的函數以及一些接口成員函數的返回值類型均為HRESULT類型。HRESULT類型的返回值反映了函數調用過程中的一些情況,而且HRESULT類型定義也有一定的規范。

HRESULT並不是指向結果結構的句柄,而是一個32位整數,通常被定義為DWORD或long類型。HRESULT的32位被分成四個域:類別碼(30-31)、自定義標志位(29)、操作碼(16-28)、操作結果碼(0-15)。

Win32 SDK的頭文件WinError.h定義了Win32函數所有的可能返回結果,其中也包括了COM庫函數以及OLE函數的返回值的宏定義。Win32 SDK提供的FormatMessage函數可根據結果值獲得一個對應於該結果值的標准說明字符串,它也支持COM庫函數的返回結果信息。

一般推薦在使用HRESULT類型作為引出函數或者作為接口成員函數的返回值時,盡量使用COM或者Win32提供的標准定義。

22、COM的實現是COM客戶程序、COM庫和COM組件程序三者之間通過COM制定的規范協同工作來完成的,三者之間形成了一個統一的整體。

23、第二章的字典組件程序是一個模擬組件程序,第三章的字典組件程序是真正的COM組件程序,這樣,在運行客戶程序之前必須先注冊組件程序,命令行為:regsvr32.exe …DictComp.dll 。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved