IDispatch接口介紹


1.         C程序調用時,調用者必須預先知道接口規范(如,參數類型、參數字節長度、參數順序等)。由於不同語言這些規范有所不同,COM未解決不同語言之間調用,提供了IDispatch接口。

2.         IDispatch要求其實例必須自我描述,即拿到一個對象后,可從對象中直接獲取調用方式,而無須預先明確。

3.         IDispatch中通過VT_TYPE來指定相關類型,如 VT_I4為4字節整形、VT_BSTR為unicode字符串,VT_DISPATCH表示是一個IDispatch對象

4.         給對象中每一屬性或函數(Method)分配一個整形Id和一個字符串name,調用者可以通過name字符串確定如何調用。如,若name為"length"的屬性,調用者就理解為長度。由於這里通常是根據name來理解相應屬性,因此name描述應足夠准確。如,以"length()"為名稱的函數實現整數相加功能就是不恰當的。

5.         使用IDispatch對象時,首相調用 IDispatch::GetIDsOfNames()將屬性、函數名稱作為參數,獲取對應的屬性、函數id。

6.         再調用IDispatch::Invoke() 將id作為參數,實際調用功能。

7.         若為獲取屬性值,則 Invoke()調用時,傳入 Dispatch_PropertyGet標志。

8.         若為設置屬性值,則Invoke()調用時,傳入 Dispatch_PropertyPut標志。並在 DispParams參數中指定修該屬性改為何值。DispParams結構說明見后。

9.         若為調用函數,則 Invoke()調用時,傳入 Dispatch_Method標志。若該Method需要參數,則通過IDispatch::Invoke()的DispParams參數指定。

10.     DispParams結構使用舉例:

DISPPARAMS dispparams;

         dispparams.rgdispidNamedArgs = &dispidOfNamedArgs;

         dispparams.cArgs = 1;

         dispparams.cNamedArgs = 1;

         dispparams.rgvarg = new VARIANTARG[1];

         dispparams.rgvarg[0].vt = VT_I4;

         dispparams.rgvarg[0].intVal = 123;

a.         上面代碼是一個用於給Method傳參的簡單例子,先創建一個DispParams對象

b.         cArgs指定Method中的參數個數。

c.         cNamedArgs指定Method中已經命名的參數個數。(命名參數是對應無名參數的概念。有些語言可定義不定參數,此時IDispatch的描述中不會給參數分配名稱,而是調用時以無名參數存在。如,JS中 Array對象的push()方法,可支持不定個數的參數)

d.         rgvarg 為實際參數數組,每一元素表示一個參數,其中.vt表明此元素的數據類型,intVal項是一個C++聯合結構,如vt == VT_I4時,應以intVal = xxx方式賦值;若 vt == VT_BSTR,則應以 bstrVal = xxx方式賦值

11.     舉例:兩個參數,都是無名稱參數,第一個為整形,第二個為BSTR型

DISPPARAMS dispparams;

         dispparams.rgdispidNamedArgs = NULL;

         dispparams.cArgs = 2;

         dispparams.cNamedArgs = 0;

         dispparams.rgvarg = new VARIANTARG[2]; // 2個參數,分配2個空間

         dispparams.rgvarg[0].vt = VT_I4;  // 整形

dispparams.rgvarg[0].intVal = 123;

dispparams.rgvarg[1].vt = VT_BSTR; // 字符串型

dispparams.rgvarg[1].bstrVal = L"abcd";

 

IDispatch接口是COM自動化的核心。其實,IDispatch這個接口本身也很簡單,只有4個方法:

 

[cpp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. IDispatch : public IUnknown  
  2. {  
  3. public:  
  4.     virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(   
  5.         /* [out] */ __RPC__out UINT *pctinfo) = 0;  
  6.       
  7.     virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(   
  8.         /* [in] */ UINT iTInfo,  
  9.         /* [in] */ LCID lcid,  
  10.         /* [out] */ __RPC__deref_out_opt ITypeInfo **ppTInfo) = 0;  
  11.       
  12.     virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(   
  13.         /* [in] */ __RPC__in REFIID riid,  
  14.         /* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR *rgszNames,  
  15.         /* [range][in] */ __RPC__in_range(0,16384) UINT cNames,  
  16.         /* [in] */ LCID lcid,  
  17.         /* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID *rgDispId) = 0;  
  18.       
  19.     virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke(   
  20.         /* [annotation][in] */   
  21.         _In_  DISPID dispIdMember,  
  22.         /* [annotation][in] */   
  23.         _In_  REFIID riid,  
  24.         /* [annotation][in] */   
  25.         _In_  LCID lcid,  
  26.         /* [annotation][in] */   
  27.         _In_  WORD wFlags,  
  28.         /* [annotation][out][in] */   
  29.         _In_  DISPPARAMS *pDispParams,  
  30.         /* [annotation][out] */   
  31.         _Out_opt_  VARIANT *pVarResult,  
  32.         /* [annotation][out] */   
  33.         _Out_opt_  EXCEPINFO *pExcepInfo,  
  34.         /* [annotation][out] */   
  35.         _Out_opt_  UINT *puArgErr) = 0;  
  36.       
  37. };  

GetTypeInfoCount和GetTypeInfo以后再說。

 

先來看看比較熟悉的GetIDsOfNames和Invoke。

GetIDsOfNames

這個函數的主要功能就是:把COM接口的方法名字和參數(可選)映射成一組DISPID。DISPID就是一個LONG型:

 

[cpp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. typedef LONG DISPID;  

GetIDsOfNames()可以獲取方法和屬性。先來看一個例子,COM接口IMyCar

 

 

[html]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. [  
  2.     object,  
  3.     uuid(21B794E2-4857-4576-8FC2-CDAB2A486600),  
  4.     dual,  
  5.     nonextensible,  
  6.     pointer_default(unique)  
  7. ]  
  8. interface IMyCar : IDispatch{  
  9.     [id(1)] HRESULT Run();  
  10.     [id(2)] HRESULT AddGas([in] LONG add, [out] LONG* total);  
  11.     [propget, id(3)] HRESULT Gas([out, retval] LONG* pVal);  
  12. };  

這個接口里面有一個方法AddGas,它有兩個參數,一個輸入,一個輸出。它的實現基本如下:

 

 

[cpp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. STDMETHODIMP CMyCar::AddGas(LONG add, LONG* total)  
  2. {  
  3.     // TODO: Add your implementation code here  
  4.     m_Gas += add;  
  5.     *total = m_Gas;  
  6.   
  7.     return S_OK;  
  8. }  

 

 

試試如何獲取AddGas函數的id和參數index,看下面的代碼。數組里面的第一個元素是方法名字,第二個/第三個是參數名字。

 

[cpp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. CComPtr<IMyCar> spCar;  
  2. spCar.CoCreateInstance(CLSID_MyCar);  

 

 

[cpp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. DISPID PropertyID[3] = {0};  
  2. BSTR PropName[3];  
  3.       
  4. PropName[0] = SysAllocString(L"AddGas");  
  5. PropName[1] = SysAllocString(L"add");  
  6. PropName[2] = SysAllocString(L"total");  
  7. HRESULT hr = spCar->GetIDsOfNames(IID_NULL, PropName, 3, LOCALE_SYSTEM_DEFAULT, PropertyID);  
  8.   
  9. SysFreeString(PropName[0]);  
  10. SysFreeString(PropName[1]);  
  11. SysFreeString(PropName[2]);  

運行一下,可以得到如下結果:

 

PropertyID數組里面可以得到3個值:2, 0, 1.

2代表的是AddGas的id,跟idl文件里面的一樣。

0表示"add"是第一個參數,1表示"total"是第二個參數。

Invoke

Invoke是IDispatch里面非常重要的一樣函數,方法調用就靠這個函數了。函數原型:

 

[cpp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. HRESULT Invoke(  
  2.   [in]       DISPID dispIdMember,  
  3.   [in]       REFIID riid,  
  4.   [in]       LCID lcid,  
  5.   [in]       WORD wFlags,  
  6.   [in, out]  DISPPARAMS *pDispParams,  
  7.   [out]      VARIANT *pVarResult,  
  8.   [out]      EXCEPINFO *pExcepInfo,  
  9.   [out]      UINT *puArgErr  
  10. );  


每一個參數的說明,看下面,從MSDN截來的。

 

先來看一個調用例子:

 

[cpp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. CComVariant avarParams[2];  
  2. avarParams[1].vt = VT_I4;  
  3. avarParams[1] = 4;  
  4.   
  5. LONG vTotal = 0;  
  6. avarParams[0].vt = VT_I4 | VT_BYREF;  
  7. avarParams[0] = &vTotal;  
  8.   
  9. DISPPARAMS params = { avarParams,  
  10.     NULL,              // Dispatch identifiers of named arguments.   
  11.     2,                 // Number of arguments.  
  12.     0 };                // Number of named arguments.  
  13.   
  14. hr = spCar->Invoke(PropertyID[0], IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, NULL, NULL, NULL);  

avarParams是一個具有2個元素的數組,它表示要調用的方法的參數,注意這里的參數是逆序的。比如avarParam[1]存放的是第一個參數,是一個輸入參數;avarParams[0]存放的是第二個參數,是一個輸出參數。

 

運行一下:

spCar里面的m_Gas初始值是0,我們調用的時候給它加了4,那么輸出就應該是4.看上面的運行結果,vTotal確實是4.

這樣我們就通過Invoke成功調用了COM組件的方法(而不是通過spCar->AddGas)。注意Invoke里面的第一個參數是一個DISPID,這個是從GetIDsOfNames來的。

 

使用Invoke也可以調用COM對象的屬性。

比如上面的idl里面的Gas。

調用屬性跟方法差不多,如果我們想讀取一個屬性,那么可以這么調:

 

[cpp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. DISPID PropertyID2[1] = { 0 };  
  2. BSTR PropName2[1];  
  3.   
  4. PropName2[0] = SysAllocString(L"Gas");  
  5.   
  6. hr = spCar->GetIDsOfNames(IID_NULL, PropName2, 1, LOCALE_SYSTEM_DEFAULT, PropertyID2);  
  7.   
  8. SysFreeString(PropName2[0]);  
  9.   
  10.   
  11. DISPPARAMS params2 = { NULL,  
  12.     NULL,  
  13.     0,  
  14.     0  
  15. };  
  16.   
  17. CComVariant Result;  
  18. hr = spCar->Invoke(PropertyID2[0], IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &params2, &Result, NULL, NULL);  


運行可以得到結果:

 


注意,Invoke的第五個參數啥都沒,屬性值是從第六個參數返回出來的。如果是方法調用的話,那么COM方法參數需要從第五個參數傳進入,第六個參數是函數調用的返回值HRESULT.

我沒有試過屬性的put,不知道是從哪里傳入,當有需要的時候查一下MSDN就行了。

 

以上就是GetIDsOfNames和Invoke的簡要說明以及例子。之后再來分析更多的細節。

完整客戶端代碼:

 

[cpp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. // ConsoleApplication4.cpp : Defines the entry point for the console application.  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5.   
  6. #include <thread>  
  7. #include <atlbase.h>  
  8. #include <atlcom.h>  
  9. #include <algorithm>  
  10. #include <vector>  
  11. #include <memory>  
  12.   
  13. #include "../MyCom/MyCom_i.h"  
  14. #include "../MyCom/MyCom_i.c"  
  15.   
  16. int _tmain(int argc, _TCHAR* argv[])  
  17. {  
  18.     CoInitializeEx(NULL, COINIT_MULTITHREADED);  
  19.       
  20.     CComPtr<IMyCar> spCar;  
  21.     spCar.CoCreateInstance(CLSID_MyCar);  
  22.   
  23.     // use IDispatch  
  24.     DISPID PropertyID[3] = {0};  
  25.     BSTR PropName[3];  
  26.           
  27.     PropName[0] = SysAllocString(L"AddGas");  
  28.     PropName[1] = SysAllocString(L"add");  
  29.     PropName[2] = SysAllocString(L"total");  
  30.     HRESULT hr = spCar->GetIDsOfNames(IID_NULL, PropName, 3, LOCALE_SYSTEM_DEFAULT, PropertyID);  
  31.   
  32.     SysFreeString(PropName[0]);  
  33.     SysFreeString(PropName[1]);  
  34.     SysFreeString(PropName[2]);  
  35.   
  36.     CComVariant avarParams[2];  
  37.     avarParams[1].vt = VT_I4;  
  38.     avarParams[1] = 4;  
  39.   
  40.     LONG vTotal = 0;  
  41.     avarParams[0].vt = VT_I4 | VT_BYREF;  
  42.     avarParams[0] = &vTotal;  
  43.   
  44.     DISPPARAMS params = { avarParams,  
  45.         NULL,              // Dispatch identifiers of named arguments.   
  46.         2,                 // Number of arguments.  
  47.         0 };                // Number of named arguments.  
  48.   
  49.     hr = spCar->Invoke(PropertyID[0], IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, NULL, NULL, NULL);  
  50.       
  51.   
  52.     DISPID PropertyID2[1] = { 0 };  
  53.     BSTR PropName2[1];  
  54.   
  55.     PropName2[0] = SysAllocString(L"Gas");  
  56.       
  57.     hr = spCar->GetIDsOfNames(IID_NULL, PropName2, 1, LOCALE_SYSTEM_DEFAULT, PropertyID2);  
  58.   
  59.     SysFreeString(PropName2[0]);  
  60.   
  61.   
  62.     DISPPARAMS params2 = { NULL,  
  63.         NULL,  
  64.         0,  
  65.         0  
  66.     };  
  67.       
  68.     CComVariant Result;  
  69.     hr = spCar->Invoke(PropertyID2[0], IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms2, &Result, NULL, NULL);  
  70.       
  71.     spCar.Release();  
  72.   
  73.     CoUninitialize();  
  74.       
  75.     return 0;  
  76. }  

相關的COM組件的代碼:

 

 

[cpp]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
    1. STDMETHODIMP CMyCar::AddGas(LONG add, LONG* total)  
    2. {  
    3.     // TODO: Add your implementation code here  
    4.     m_Gas += add;  
    5.     *total = m_Gas;  
    6.   
    7.     return S_OK;  
    8. }  
    9.   
    10.   
    11. STDMETHODIMP CMyCar::get_Gas(LONG* pVal)  
    12. {  
    13.     // TODO: Add your implementation code here  
    14.     *pVal = m_Gas;  
    15.   
    16.     return S_OK;  
    17. }  


免責聲明!

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



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