1. 基本詳情
IDispatch是由OLE自動化協議暴露出來的接口。
IDispatch可以由IUnknown得到,並且在IUnknown本身所含有三個方法(AddRef,Release和QueryInterface)上增加另外四個方法(GetTypeInfoCount,GetTypeInfo,GetIDsOfNames和Invoke)。
一個COM組件實現了IDispatch接口就成為自動化組件。
2. 為什么需要IDispatch接口
如果是編譯型語言,那么我們可以讓編譯器在編譯的時候裝載類型庫(tlb),也就是裝載接口的描述。我們分別使用了 #include 方法和 #import 方法來實現的。裝載了類型庫后,編譯器就知道應該如何編譯接口函數的調用了—這叫“前綁定”。
但是,如果想在腳本語言中使用組件,問題就大了,因為腳本語言是解釋執行的,無法裝載類型庫進行預編譯,它執行的時候不會知道具體的函數地址,怎么辦?自動化接口就為此誕生了—“后綁定”。
自動化組件,其實就是實現了 IDispatch 接口的組件。所以,當需要腳本或解釋性語言調用COM組件時才去實現IDispatch接口。
3. 通過IDispatch接口的執行效率
通過IDispatch接口去調用函數,要先通過GetIDsOfNames獲取函數ID號,然后Invoke通過這個ID號去調用函數,是一種間接的調用方法,效率相對與通過“前綁定”的方法來的低。
正因為這樣,ATL產生了一種雙接口的模式,在這種模式下,編譯型語言會通過前綁定的方法來調用函數,而解釋性語言中通過自動化接口來調用,這樣兼顧了兩邊。
4. 使用IDispatch接口的缺點
1). 為了在各種語言中通訊,必須要使用Variant類型。
2). 因為是間接調用,效率相對比較低。
5. 使用雙重接口
在實現調度接口時,更好的方法是雙重接口,雙重接口使得C++程序員能夠通過接口的虛函數表訪問到,而在宏語言和解釋性語言則通過自動化接口去調用。
所謂雙接口,其實是在一個 VTAB 的虛函數表中容納了三個接口(因為任何接口都是從 IUnknown 派生的,所以就不強調 IUnknown 了,叫做雙接口)。我們如果從任意一個接口中調用 QueryInterface()得到另外的接口指針的話,其實,得到的指針地址都是同一個。
函數既可以通過接口IXXX通過Vtabl表直接調用,這樣效率高,用於編譯型語言,同時又可通過獲取調度ID用Invoke去調用,效率相對低,一般在解釋性語言中使用。
當然也可在編譯型語言中通過Invoke去調用,但就相當麻煩,如下:(不過,通過這種方法,可以不加載類型庫或頭文件)
1 ::CoInitialize( NULL ); // COM 初始化
2
3 CLSID clsid; // 通過 ProgID 得到 CLSID
4 HRESULT hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &clsid ); 5 ASSERT( SUCCEEDED( hr ) ); // 如果失敗,說明沒有注冊組件
6 IDispatch * pDisp = NULL; 7 // 由 CLSID 啟動組件,並得到 IDispatch 指針
8 hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IDispatch, (LPVOID *)&pDisp ); 9 ASSERT( SUCCEEDED( hr ) ); // 如果失敗,說明沒有初始化 COM
10 LPOLESTR pwFunName = L"Add"; // 准備取得 Add 函數的序號 DispID
11 DISPID dispID; // 取得的序號,准備保存到這里
12 hr = pDisp->GetIDsOfNames( // 根據函數名,取得序號的函數
13 IID_NULL, 14 &pwFunName, // 函數名稱的數組
15 1, // 函數名稱數組中的元素個數
16 LOCALE_SYSTEM_DEFAULT, // 使用系統默認的語言環境
17 &dispID ); // 返回值
18 ASSERT( SUCCEEDED( hr ) ); // 如果失敗,說明組件根本就沒有 ADD 函數
19 VARIANTARG v[2]; // 調用 Add(1,2) 函數所需要的參數v[0].vt = VT_I4; v[0].lVal = 2; // 第二個參數,整數2v[1].vt = VT_I4; v[1].lVal = 1; // 第一個參數,整數1
20 DISPPARAMS dispParams = { v, NULL, 2, 0 }; // 把參數包裝在這個結構中
21 VARIANT vResult; // 函數返回的計算結果
22 hr = pDisp->Invoke( // 調用函數
23 dispID, // 函數由 dispID 指定
24 IID_NULL, 25 LOCALE_SYSTEM_DEFAULT, // 使用系統默認的語言環境
26 DISPATCH_METHOD, // 調用的是方法,不是屬性
27 &dispParams, // 參數
28 &vResult, // 返回值
29 NULL, // 不考慮異常處理
30 NULL); // 不考慮錯誤處理
31 ASSERT( SUCCEEDED( hr ) ); // 如果失敗,說明參數傳遞錯誤
32 CString str; // 顯示一下結果
33 str.Format("1 + 2 = %d", vResult.lVal ); 34 AfxMessageBox( str ); 35
36 pDisp->Release(); // 釋放接口指針
37 ::CoUninitialize(); // 釋放 COM
6. IDispatch接口代碼參考
IDispatch及雙接口的調用
① 使用API方式調用:
1 ::CoInitialize( NULL ); // COM 初始化
2
3 CLSID clsid; // 通過 ProgID 得到 CLSID
4 HRESULT hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &clsid ); 5 ASSERT( SUCCEEDED( hr ) ); // 如果失敗,說明沒有注冊組件
6
7 IDispatch * pDisp = NULL; // 由 CLSID 啟動組件,並得到 IDispatch 指針
8 hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IDispatch, (LPVOID *)&pDisp ); 9 ASSERT( SUCCEEDED( hr ) ); // 如果失敗,說明沒有初始化 COM
10
11 LPOLESTR pwFunName = L"Add"; // 准備取得 Add 函數的序號 DispID
12 DISPID dispID; // 取得的序號,准備保存到這里
13 hr = pDisp->GetIDsOfNames( // 根據函數名,取得序號的函數
14 IID_NULL, 15 &pwFunName, // 函數名稱的數組
16 1, // 函數名稱數組中的元素個數
17 LOCALE_SYSTEM_DEFAULT, // 使用系統默認的語言環境
18 &dispID ); // 返回值
19 ASSERT( SUCCEEDED( hr ) ); // 如果失敗,說明組件根本就沒有 ADD 函數
20
21 VARIANTARG v[2]; // 調用 Add(1,2) 函數所需要的參數
22 v[0].vt = VT_I4; v[0].lVal = 2; // 第二個參數,整數2
23 v[1].vt = VT_I4; v[1].lVal = 1; // 第一個參數,整數1
24
25 DISPPARAMS dispParams = { v, NULL, 2, 0 }; // 把參數包裝在這個結構中
26 VARIANT vResult; // 函數返回的計算結果
27
28 hr = pDisp->Invoke( // 調用函數9 dispID, // 函數由 dispID 指定
29 IID_NULL, 30 LOCALE_SYSTEM_DEFAULT, // 使用系統默認的語言環境
31 DISPATCH_METHOD, // 調用的是方法,不是屬性
32 &dispParams, // 參數
33 &vResult, // 返回值
34 NULL, // 不考慮異常處理
35 NULL); // 不考慮錯誤處理
36 ASSERT( SUCCEEDED( hr ) ); // 如果失敗,說明參數傳遞錯誤
37
38 CString str; // 顯示一下結果
39 str.Format("1 + 2 = %d", vResult.lVal ); 40 AfxMessageBox( str ); 41
42 pDisp->Release(); // 釋放接口指針
43 ::CoUninitialize(); // 釋放 COM
② 使用智能指針包裝類:
1 // 在 App 類,InitInstance 中,已經調用 AfxOleInit() 進行了 COM 初始化
2
3 CLSID clsid; // 通過 ProgID 取得組件的 CLSID
4 HRESULT hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &clsid ); 5 ASSERT( SUCCEEDED( hr ) ); // 如果失敗,說明沒有注冊組件
6
7 CComPtr < IUnknown > spUnk; // 由 CLSID 啟動組件,並取得 IUnknown 指針
8 hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IUnknown, (LPVOID *)&spUnk ); 9 ASSERT( SUCCEEDED( hr ) ); 10
11 CComDispatchDriver spDisp( spUnk ); // 構造只能指針
12 CComVariant v1(1), v2(2), vResult; // 參數
13 hr = spDisp.Invoke2( // 調用2個參數的函數
14 L"Add", // 函數名是 Add
15 &v1, // 第一個參數,值為整數1
16 &v2, // 第二個參數,值為整數2
17 &vResult); // 返回值
18 ASSERT( SUCCEEDED( hr ) ); // 如果失敗,說明或者沒有 ADD 函數,或者參數錯誤
19 CString str; // 顯示一下結果
20 str.Format("1 + 2 = %d", vResult.lVal ); 21 AfxMessageBox( str ); 22
23 // spUnk 和 spDisp 都是智能指針,會自動釋放 24 // 如果使用 CoInitialize(NULL) 初始化,則必須在 CoUninitialize() 之前 25 // 調用 spUnk.Release() 和 spDisp.Release() 釋放