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, ¶ms, 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, ¶ms2, &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 ¶mArray, _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)){...}
附:
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>代替。