C++ COM編程-IDispatch介紹


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() 釋放

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM