1、COM的高級特性
COM規范中有一些高級特性,如可連接對象、永久存儲、一致的數據傳輸等,使COM規范具有更強的生命力,它們也是OLE的基礎,但它們的應用又不僅僅局限於OLE,這些高級特性已經廣泛應用於Windows操作系統上。
2、入接口、出接口與接收器
入接口(incoming interface)是組件暴露給客戶,被動地監聽並為客戶請求作出反應的接口。而出接口(outgoing interface)是指組件主動與客戶進行通信的接口。
出接口不是由對象實現的,而是由客戶程序來實現,客戶實現這些接口,並把接口指針告訴對象,以後對象利用此接口指針與客戶進行通信。在客戶程序方,實現這些接口的對象被稱為接收器(sink)。接收器本身也是一個COM對象,但它往往比較簡單,只用於監聽處理組件對象的通知或請求。
客戶與對象之間的關系是相對的,入接口和出接口也是一個相對概念,它們只用於通信的一個方向。
3、可連接對象
如果一個COM對象支持一個或多個出接口,則稱這樣的對象為可連接對象(connectable object),或稱為源對象(source)。
可連接對象的出接口也是COM接口,它包含一組成員函數,每個成員函數代表了一個事件(event)、一個通知(notification)或者一個請求(request)。
4、事件、通知與請求
事件和通知在概念上是完全一致的,只是用在不同的場合,例如在COM對象中當某個屬性被改變時,它可以給客戶發送一個通知;而當特定事情發生時,比如定時消息或用戶鼠標操作發生時,對象產生一個事件,客戶程序可以處理這些事件。然而,請求的概念則稍有不同,對象給客戶發出請求,它希望客戶能提供某些信息,期望客戶能有應答。
從COM規范的意義上來講,不管是事件、通知還是請求,它們都通過出接口的成員函數來實現。
5、客戶與可連接對象的關系
雖然接收器也是一個COM,但它有特殊性,它位於客戶程序內部,並不需要通過COM庫來創建,所以接收器並不需要CLSID來標識,也不需要類廠,接收器的標識和創建過程完全是客戶程序內部的事情。對於客戶程序外部而言,接收器也是一個單獨的COM對象,它有自己的引用計數,有自己的接口查詢方法,即 QueryInterface成員函數。COM只要求接收器是一個獨立的COM對象,COM規范對接收器的實現沒有任何限制。
一般可連接對象不應該向接收器對象請求其他的接口,即不應該調用接收器的QueryInterface成員函數。接收器通常專用於某個出接口指針,接收器對象只實現該出接口,當然出接口是基接口(比如IUnknown)除外。
可連接對象和接收器可以形成一對多或者多對一的關系,也即客戶與可連接對象之間可以是一對多或者多對一的關系。
6、可連接對象的基本結構
可連接對象可以支持一個或多個出接口,它通過接口IConnectionPointContainer管理所有的出接口。對應於每個出接口,可連接對象又管理了一個稱為連接點(connection point)的對象,每一個連接點對象實現了IConnectionPoint接口,客戶通過連接點對象建立接收器與可連接對象的連接。連接點即可以訪問可連接對象的內部信息,也可以訪問客戶方的接收器,而其它可以直接使用可連接對象的引用計數器。
7、枚舉器
在COM規范中,枚舉器(Enumerator)只是一個概念,沒有確定的接口用於規定枚舉器的各項操作,這是因為枚舉器所枚舉的數據單元的類型不確定,所以也無法給出確切的定義。客戶程序利用枚舉器對COM對象中的數據單元進行枚舉操作,枚舉器把客戶對數據單元的操作進行了標准化,因此,COM對象可以按照標准的方法把數據提供給客戶,而不必建立二者之間新的協議。
因為枚舉器對象是一個內部對象,它只需暴露枚舉接口,不需要CLSID和類廠,所以枚舉器對象的實現比較簡單,只需實現枚舉操作並控制好引用計數即可。
客戶的接收器與源對象的連接點建立連接時,源對象使用連接點枚舉器管理連接點對象,連接點對象又用連接枚舉器管理連接,通過兩層結構建立對象與接收器之間的連接。把源對象與連接點對象分開實現,可以使它們各自保持一定的獨立性。源對象與接收器之間的連接具有很好的擴展性,而且連接點對象的獨立性也使得COM可連接對象機制更具靈活性和廣泛性。
8、接收器的實現
在C++語言中,用一般的類從接口類派生,然後分別實現接口成員函數即可。客戶程序在建立連接之前,要先創建接收器對象,因為接收器是客戶程序的內部對象,所以在C++語言中可以用new操作符創建接收器對象,然後用此連接器對象建立它與源對象之間的連接。
9、事件的激發和處理
實現事件和請求是可連接對象機制的主要目標。事件即可由源對象的入接口成員函數激發,也可以由用戶的某些操作引起,還可以由其它對象或客戶調用而引起。總之,在源對象執行過程中,根據需要都可以激發事件或者向客戶發出請求,事件和請求在程序邏輯上完全一致。
由於事件或請求是在每個連接上進行的,只有建立了連接的接收器對象才會收到事件或請求。
連接點對象和接收器對象肯能位於不同的進程中,甚至在不同的機器環境中。因此,事件從激發到處理不一定是直接的函數調用,這是可連接對象機制與一般回調函數機制的重要不同。只有在單線程模型下,連接點對象才直接調用接收器對象的事件控制函數。從連接點對象到接收器對象之間的通信過程與以前討論的客戶和對象之間的通信過程一樣,也符合COM線程模型規范,必要時侯也需要進行列集處理。
10、出接口通信連接的建立
首先客戶方通過源對象的IConnectionPointContainer接口得到源對象的出接口IID,並進一步向源對象請求 IProvideClassInfo接口,調用IProvideClassInfo::GetClassInfo成員函數得到ITypeInfo結構,再進一步得到出接口的ITypeInfo結構而獲取接口的所有類型信息,包括成員函數、函數參數的個數和參數類型等。類型信息是客戶和源對象雙方的通信協議標准。
根據出接口的類型信息在程序運行過程中實現動態接收器對象很不容易。雖然可連接對象提供了完善的雙向通信機制,但客戶要在運行過程中根據源對象的類型信息響應事件或請求並不容易。為此,OLE發展了COM的可連接對象的機制,它使用IDispatch接口作為出接口,利用IDispatch接口中方法(method)的分發功能實現事件控制函數。IDispatch接口的主要特點是,它可在運行時刻而不是在編譯時刻把成員函數與特定的分發ID進行綁定操作,這種特性稱為遲綁定(late binging)。IDispatch接口是Microsoft實現自動化技術的基礎,現在已經得到了廣泛的應用。
11、用IDispatch接口作為出接口
IDispatch接口是自動化對象的基本接口,在高級語言或者腳本語言中,可以直接用符號化的名字即字符串訪問自動化對象的屬性(property)和方法(method)。使用IDispatch接口有三方面的顯著有點:第一,用名字訪問屬性和方法非常簡單易用;第二,自動化對象的IDispatch 接口的vtable是固定的,在有些高級語言或腳本語言中沒有指針數據類型,所以在這些語言中描述自定義接口比較困難;第三,IDispatch接口支持遲綁定特性,可以在運行過程中根據名字訪問屬性或方法。
COM已經提供了IDispatch接口的代理對象(proxy)和存根對象(stub),所以,使用IDispatch接口作為出接口可直接用於進程外源對象的出接口。
IDispatch接口把所有的調用都通過其成員函數Invoke來實現,並且它提供了管理屬性和方法的分發ID機制,以及一套描述參數和返回值的方法,所以使得運行時刻動態綁定屬性和方法並進行參數類型檢查成為可能。可以說Invoke函數是自動化對象的命令翻譯器。
根據不同的開發環境和運行環境,實現Invoke函數可以采用不同的方法。如果在編譯時刻可以決定客戶應該響應那些事件或請求,則可以在程序中建立一張表,把每個事件或請求的分發ID和對應的控制函數作為表項放到表中,把這張表稱為事件映射表。MFC的COleControl類使用這種方法處理 ActiveX控制的事件和請求。
利用IDispatch接口作為出接口可以很好地解決接收器的動態創建過程。利用IDispatch接口作為源對象的出接口,由源對象提供出接口的類型信息,即事件控制函數的所有信息,客戶程序根據這些類型信息,在Invoke函數中調用相應的事件控制函數。
12、MFC對連接和事件的支持
(1)MFC實現了連接點類CConnectionPoint,CConnectionPoint實現了IConnectionPoint接口,它用一個數組枚舉器管理連接;
(2)CCmdTarget也提供了一組宏支持連接點對象;
(3)CCmdTarget類有一個內嵌的結構成員m_xConnPtContainer專門用於存放接口IConnectionPointContainer的vtable和偏移量;
(4)連接點是可連接對象的核心,但連接點的主要目的是激發事件或發送請求,因此,我們應該對每個事件或請求編寫一個激發函數。
MFC提供了類COleDispatchDriver,他主要用於IDispatch接口的客戶方調用操作,利用COleDispatchDriver的成員函數,客戶可以創建自動化對象,也可以把COleDispatchDriver對象與某個自動化對象聯系起來,更有意義的是,COleDispatchDriver使得IDispatch::Invoke調用的參數處理更為簡單。
13、用CCmdTarget實現源對象的程序結構圖