COM學習筆記8_IDispatch (調度接口) 自動化

一般的通訊方式:
客戶 <==> COM(vbtl)接口 <==> COM組件

自動化通訊方式:
客戶(自動化控制器) <==> IDispatch::Invoke <==> 調度接口(或vbtl接口) <==> 實現IDispatch接口的COM組件 (自動化服務器)

自動化服務器 : COM組件
自動化控制器 :COM客戶

相關知識:IDispatch, 調度接口,雙重接口,類型庫,IDL, VARIANT, BSTR
調度接口(dispinterface) :IDispatch::Invoke的一個實現所能調用的函數集合,客戶只能通過IDispatch::Invoke使用組件
COM(vbtl)接口(custome) : 一個指針,指向一個函數指針數組,數組前三個元素是 QueryInterface,AddRef和Release
雙重接口(dual) :客戶既可以通過調度接口(IDispatch::Invoke),也可以直接通過COM接口(vbtl調用)使用組件

一般C++程序直接使用抽象接口調用COM組件,而編譯器會進行地址映射。例如:
pIX->Fx (msg) ;
實際會被編譯成這樣:
(*(pIX->vbtl [IndexOfFx]))(pIX, msg) ;
具體如下:
1. 獲取Fx在虛函數表中的索引 IndexOfFx = 4
2. 獲取Fx的函數地址 pAddressOfFx = pIX->vbtl [IndexOfFx]
3. 解引用,調用函數 (注意需要傳入this指針) (*pAddressOfFx)(pIX, msg)
上面三步合成就是 (*(pIX->vbtl [IndexOfFx]))(pIX, msg) ;了

但問題在於像VB, Javascript等沒有指針的概念,如何做到上面幾步,獲取vbtl中的函數指針呢?
可以編寫一個C++分析器處理 (相當於加入了一箇中間層)
中間層關鍵要處理三種信息 : 組件的ProgID, 函數名稱,參數
這個中間層通過IDispatch接口實現,其原型:

 

其中比較重要的有GetIDsOfNames 和 Invoke。

Invoke參數說明:
1. DISPID dispIdMember : 標誌客戶待調用的函數名,可由GetIDsOfNames獲得
2. REFIID riid : 必須爲 IID_NULL
3. LCID lcid : 用戶本地化信息,可用 GetUserDefaultLCID() 獲取
4. WORD wFlags : 一個函數名稱其實可以和四個函數關聯 (常規函數,設置屬性函數,通過引用設置屬性函數,獲取屬性函數),
                 它的值可以是DISPATCH_METHOD, DISPATCH_PROPERTYPUT, DISPATCH_PROPERTYPUTREF, DISPATCH_PROPERTYGET.
5. DISPPARAMS *pDispParams : 參數列表,其定義如下:

6. VARIANT *pVarResult :保存函數或propget的結果,沒有返回值時爲NULL
7. EXCEPINFO *pExcepInfo :保存例外情況的信息,可參考C++異常處理。當Invoke返回DISP_E_EXCEPTION,DISP_E_PARAMNOTFOUND等
                           時,可查詢pExcepInfo中相關信息。


VARIANT 其實是一個標準類型的大枚舉,定義大概如下:

 

VARIANT 通過VariantInit初始化,VariantInit將vt設爲VT_EMPTY。
可通過VariantChangeType轉化VARIANT的類型
對調度接口中的可選參數,可設vt爲VT_ERROR,scode爲DISP_E_PARAMNOTFOUND。

VARIANT中比較特殊的BSTR和SAFEARRAY類型。
BSTR :它是(Basic String)或(binary string)的縮寫。定義如下:

但BSTR帶有字符計數值,這個值保存在字符數組開頭,所以BSTR字串中可以有多個'/0'。
所以下面方法錯誤:

應該使用SysAllocString給BSTR賦值,使用SysFreeString釋放:


SAFEARRAY :包含邊界信息的數組

fFeatures 表示SAFEARRAY中數據的類型
自動化庫OLEAUT32.Dll中有一系列操作SAFEARRAY的函數,以SafeArray爲前綴

 

一個調度接口的可能實現:

一個調度接口的可能實現

 

一個雙重接口的可能實現

一個雙重接口的可能實現

 

調度接口最好用雙重接口實現,這樣C++程序員可直接通過vbtl調用函數。
IDispatch::Invoke的兩個主要缺點:
1. 效率低,(進程內組件可能差幾個數量級,進程外甚至遠程組件就不明顯了)
2. 參數只能用標準參數

類型庫:
有了Invoke, VB或C++程序可以在不知道接口的任何類型信息下控制組件(當然程序員還是需要閱讀文檔知道接口的參數細節),
但這樣做需要運行時類型檢查和轉換,這樣開銷很大,並且可能隱藏錯誤。
所以COM提供類型庫,只是一種語言無關,適合解釋性語言的C++頭文件等價物。
類型庫提供組件,接口,方法,屬性,參數,接口等類型信息。
它是一個二進制文件,是IDL文件的一個編譯版本。
有了類型庫,VB也可以通過組件雙重接口的Vtbl部分訪問組件。

類型庫可由CreateTypeLib創建,他返回ICreatetypeLib接口,這種方式很少用。
類型庫可在IDL中聲明,通過MIDL編譯 (TLB後綴,也可包含在exe或dll中)
它包括一個GUID, 一個版本號和一個幫助字符串
coclass 定義一個組件

 

類型庫的使用
1. 裝載
LoadRegTypeLib,從註冊表中裝載
LoadTypeLib, 從硬盤上裝載(裝載庫時會自動註冊,但若提供完整路徑名則不會註冊,需要調用RegisterTypeLib註冊)
LoadLibFromResource 從Exe/Dll中裝載

示例:

 

裝載完成後,得到一個ITypeLib接口指針,可以調用ITypeLib::GetTypeInfoOfGuid再獲取某組件或接口的信息,他返回一個ITypeInfo指針
ITypeInfo指針可以獲取組件,接口,方法,屬性,結構和其他類似的任何信息
不過一般C++組件程序員只將它用於實現IDispatch接口,實現IDispatch接口可以簡單的將GetIDsOfNames和Invoke轉發給對應的ITypeInfo指針

 

類型庫註冊
在註冊表的HKEY_CLASSED_ROOT/TypeLib下

異常的引發
給Invoke的EXCEPINFO結構參數填充信息
1. 組件實現ISupportErrorInfo接口

 

2. IDispatch::Invoke實現中,調用ITypeInfo::Invoke前先調用

 

3. 發生異常時,調用CreateErrorInfo獲取ICreateErrorInfo接口指針
   使用ICreateErrorInfo接口指針填充錯誤信息
   調用SetErrorInfo填充

發佈了59 篇原創文章 · 獲贊 9 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章