IDispatch接口 - GetIDsOfNames和Invoke

 

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

 

 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型:

1 typedef LONG DISPID;

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

 

 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,它有兩個參數,一個輸入,一個輸出。它的實現基本如下:

 

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,看下面的代碼。數組裏面的第一個元素是方法名字,第二個/第三個是參數名字。

1     CComPtr<IMyCar> spCar;
2     spCar.CoCreateInstance(CLSID_MyCar);

 

 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裏面非常重要的一樣函數,方法調用就靠這個函數了。函數原型:

 

 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截來的。

 

先來看一個調用例子:

 

 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。

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

 

 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);
19     

 

 

運行可以得到結果:

 

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

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

 

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

完整客戶端代碼:

 

 

 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組件的代碼:

 

 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 }

 

 上述摘自:http://blog.csdn.net/zj510/article/details/39494873

 

引申(原創):

1、CHtmlDialog調用js ,無參數有返回值情況:

js函數如下:

1 function GetFileChange()
2 {
3     return bFileChange;
4 }

MFC調用js函數如下:

 

 1 BOOL CHTMLDialogDlg::CallJSScript(const CString strFunc, _variant_t* pVarResult)
 2 {
 3     CComPtr<IDispatch> spScript;
 4     if(m_spHtmlDoc==NULL)
 5         return FALSE;
 6     HRESULT hr = m_spHtmlDoc->get_Script(&spScript);
 7     if(!SUCCEEDED(hr))
 8     {
 9         return FALSE;
10     }
11     CComBSTR bstrFunc(strFunc);
12     DISPID dispid = NULL;
13     HRESULT hrr = spScript->GetIDsOfNames(IID_NULL, &bstrFunc, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
14     if(FAILED(hrr))
15     {
16         return FALSE;
17     }
18 
19     DISPPARAMS dispparams;
20     memset(&dispparams, 0, sizeof(dispparams));
21     
22     EXCEPINFO excepInfo;
23     memset(&excepInfo, 0, sizeof(excepInfo));
24     _variant_t vaResult;
25     UINT nArgErr = (UINT)-1;
26     hrr = spScript->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, &dispparams, &vaResult, &excepInfo, &nArgErr);
27     delete[] dispparams.rgvarg;
28 
29     if(FAILED(hrr))
30     {
31         return FALSE;
32     }
33     if(pVarResult)
34     {
35         *pVarResult = vaResult;
36     }
37     return TRUE;
38 }

 

調用如下

1 if(CallJSScript("GetFileChange", &retParam)){
2     if(retParam.vt == VT_BOOL){
3         bFileChange = retParam.boolVal;
4     }
5  }

 2、CHtmlDialog調用js ,有參數有返回值情況:

1 function OpenArgvFile(strArgvFile){
    .....
2     return bRet;
3 }

MFC調用js函數如下:

 

 1 BOOL CHtmlDialogDlg::CallJSScript(const CString strFunc, const CStringArray &paramArray, _variant_t* pVarResult)
 2 {
 3     CComPtr<IDispatch> spScript;
 4     if(m_spHtmlDoc==NULL)
 5         return FALSE;
 6     HRESULT hr = m_spHtmlDoc->get_Script(&spScript);
 7     if(!SUCCEEDED(hr))
 8     {
 9         return FALSE;
10     }
11     CComBSTR bstrFunc(strFunc);
12     DISPID dispid = NULL;
13     HRESULT hrr = spScript->GetIDsOfNames(IID_NULL, &bstrFunc, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
14     if(FAILED(hrr))
15     {
16         return FALSE;
17     }
18 //
19     const int arraySize = paramArray.GetSize();
20 
21     DISPPARAMS dispparams;
22     memset(&dispparams, 0, sizeof dispparams);
23     dispparams.cArgs = arraySize;
24     dispparams.rgvarg = new VARIANT[dispparams.cArgs];
25 
26     for( int i = 0; i < arraySize; i++)
27     {
28         CComBSTR bstr = paramArray.GetAt(arraySize - 1 - i); // back reading
29         bstr.CopyTo(&dispparams.rgvarg[i].bstrVal);
30         dispparams.rgvarg[i].vt = VT_BSTR;
31     }
32     dispparams.cNamedArgs = 0;
33 //
34     //DISPPARAMS dispparams;
35     //memset(&dispparams, 0, sizeof(dispparams));
36     
37     EXCEPINFO excepInfo;
38     memset(&excepInfo, 0, sizeof(excepInfo));
39     _variant_t vaResult;
40     UINT nArgErr = (UINT)-1;
41     hrr = spScript->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, &dispparams, &vaResult, &excepInfo, &nArgErr);
42     delete[] dispparams.rgvarg;
43 
44     if(FAILED(hrr))
45     {
46         return FALSE;
47     }
48     if(pVarResult)
49     {
50         *pVarResult = vaResult;
51     }
52     return TRUE;
53 }

 

調用如下

1 CStringArray paramArray;
2             _variant_t retParam;
3             paramArray.Add(m_strArgvFile);
4             if(!CallJSScript("OpenArgvFile", paramArray, &retParam)){...}

 

附:

CComPtr用法

COM接口指針很危險,因爲使用過程中需要每一個使用者都要嚴格並且正確的AddRef和Release,一旦出現問題,就會造成對象不能被正常釋放,或者對象被重複刪除,造成程序崩潰。所以使用COM接口,必須小心翼翼才行。
但是,即使所有的代碼中,都正確的AddRef和Release,也不一定能保證萬無一失,例如:
void SomeApp( IHello * pHello )
{
IHello* pCopy = pHello;
pCopy->AddRef(); 
OtherApp();
pCopy->Hello();
pCopy->Release();
}
看起來好像無懈可擊,但是假設OtherApp中拋出了異常,那麼pCopy->Release不就被跳過去了嗎?
幸好,所有的問題都從簡單到複雜,再從複雜到簡單的,因爲我們有CComPtr!

CComPtr被稱爲智能指針,是ATL提供的一個模版類,能夠從語法上自動完成AddRef和Release。(源代碼在atlbase.h中)
CComPtr的用法很簡單,以IHello*爲例,將程序中所有接口指針類型(除了參數),都使用CComPtr<IHello> 代替即可。即程序中除了參數之外,再也不要使用IHello*,全部以CComPtr<IHello>代替。
CComPtr的用法和普通COM指針幾乎一樣,另外使用中有以下幾點需要注意。
1. CComPtr已經保證了AddRef和Release的正確調用,所以不需要,也不能夠再調用AddRef和Release。
2. 如果要釋放一個智能指針,直接給它賦NULL值即可。(這一點要牢記曾因爲沒有設置爲null而出錯)
3. CComPtr本身析構的時候會釋放COM指針。
4. 當對CComPtr使用&運算符(取指針地址)的時候,要確保CComPtr爲NUL。(因爲通過CComPtr的地址對CComPtr賦值時,不會自動調用AddRef,若不爲NULL,則前面的指針不能釋放,CComPtr會使用assert報警)
以剛纔的程序爲例:
void SomeApp( IHello * pHello )
{
CComPtr<IHello> pCopy = pHello;
OtherApp();
pCopy->Hello();
}
由於pCopy是一個局部的對象,所以即使OtherApp()拋出異常,pCopy也會被析構,指針能夠被釋放。
如果不想在程序臨近發佈前,還因爲COM指針的引用計數造成崩潰的話,就牢記這一點吧:程序中除了參數之外,不要直接使用COM指針類型,一定要全部以CComPtr<IXXX>代替。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章