前言:
盡管com接口是跨語言的,但是很多語言在使用com時更多地通過Automation技術來和com對象通信。IDispatch接口是Automation的核心技術。
盡管c++程式員并不喜歡甚至讨厭使用IDispatch接口,因為調用它實在是非常的麻煩而且易出錯。但是不可否認大量的現存元件是隻基于IDispatch接口技術而開發的,有時候你沒有選擇,而且如果你想要寫一些元件能夠在web上運作,你也離不開IDisptch接口,因為VBScript這樣的腳本語言不會聰明到能夠了解你的基于虛函數表的普通com接口。
與其躲避它,不如征服它。本文中,我将結合自己的經驗和讀者一起探讨IDispatch接口的各種應用。并介紹幾種能夠加快我們使用IDispatch接口的c++類。
IDispatch接口的定義:
參照檔案oaidl.h中的定義----
MIDL_INTERFACE("00020400-0000-0000-C000-000000000046")
IDispatch : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(
UINT *pctinfo) = 0;
virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(
UINT iTInfo,
LCID lcid,
ITypeInfo **ppTInfo) = 0;
virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(
REFIID riid,
LPOLESTR *rgszNames,
UINT cNames,
LCID lcid,
DISPID *rgDispId) = 0;
virtual HRESULT STDMETHODCALLTYPE Invoke(
DISPID dispIdMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS *pDispParams,
VARIANT *pVarResult,
EXCEPINFO *pExcepInfo,
UINT *puArgErr) = 0;
};
我們通過IDispatch的GUID到系統資料庫中搜尋,可以搜尋到如下結果:
注意在IDispatch接口GUID下面還有兩個展開的GUID項,他們分别是ITypeInfo和ITypeLib接口。這兩個接口在自動化應用中也是非常重要的。今後我們會經常看到他們。
IDispatch接口方法簡介:
1)HRESULT STDMETHODCALLTYPE GetTypeInfoCount(
UINT *pctinfo) ;
判斷實作了IDispatch接口的對象是否支援類型資訊,如果傳回1則支援,傳回0則不支援。
2) HRESULT STDMETHODCALLTYPE GetTypeInfo(
UINT iTInfo,
LCID lcid,
ITypeInfo **ppTInfo) = 0;
擷取對象的類型資訊接口指針,該方法調用之前總應該先調用方法GetTypeInfoCount(…)确認是否支援類型資訊。
參數iTInfo必須為0,否則該方法将傳回DISP_E_BADINDEX表示失敗
參數lcid傳遞類型資訊的地域标志。IDispatch接口的方法和屬性在不同的語言環境(地域标志)可以使用不同的名稱,因而lcid不同可能會導緻傳回的ITypeInfo接口指針不同。如果我們建立的元件根據不同的地域标志對屬性和方法起不同的名字,我們就要使用這個參數,否則可以忽略。
3)HRESULT STDMETHODCALLTYPE GetIDsOfNames(
REFIID riid,
LPOLESTR *rgszNames,
UINT cNames,
LCID lcid,
DISPID *rgDispId)
IDispatch接口的屬性實質上是方法,方法也就是成員函數,IDispatch接口把所有成員函數的入口位址放入到一個數組中,并且内部組織了一個Map,将數組索引和方法名稱一一映射。我們常見的DISPID就是這些方法在數組中的索引。如果我們想調用某一個方法,我們就需要DISPID來讓我們找到該方法的位址。
參數riid必須為NULL。
參數rgszNames為字元串數組,第一個字元串為方法或者屬性的名稱,後續的字元串為參數名稱,IDispatch接口的參數也可以有名字。
參數cNames指定rgszNames數組中字元串的個數。
參數lcid傳遞地域标志,同GetTypeInfo方法中的參數。
參數rgDispId輸出一個數組,每個數組成員對應rgszNames中的一個字元串名稱。
關于DISPID的進一步說明:
typedef LONG DISPID;
typedef DISPID MEMBERID;
DISPID小于等于0的值都是有特殊意義的,如下面介紹的----
//如果GetIDsOfNames函數找不到與名稱相對應的DISPID,傳回該值
#define DISPID_UNKNOWN ( -1 )
//如果調用時不指定方法或者屬性,則使用該預設值
#define DISPID_VALUE ( 0 )
//表明屬性設定函數中某一個參數将接受新屬性值
#define DISPID_PROPERTYPUT ( -3 )
//用于集合對象
#define DISPID_NEWENUM ( -4 )
#define DISPID_EVALUATE ( -5 )
#define DISPID_CONSTRUCTOR ( -6 )
#define DISPID_DESTRUCTOR ( -7 )
#define DISPID_COLLECT ( -8 )
4) HRESULT STDMETHODCALLTYPE Invoke(
DISPID dispIdMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS *pDispParams,
VARIANT *pVarResult,
EXCEPINFO *pExcepInfo,
UINT *puArgErr)
參數dispIdMember為方法或者屬性的DISPID,就是我們通過GetIDsOfNames獲得的。
參數riid必須為IID_NULL。
參數lcid為地域标志,同前面兩個方法。
參數wFlags有下面若幹值----
Value
Description
DISPATCH_METHOD
表示将調用方法。如果屬性名稱和方法名稱相同,則和DISPATCH_PROPERTYGET标志一起設定。
DISPATCH_PROPERTYGET
獲得屬性
DISPATCH_PROPERTYPUT
設定屬性
DISPATCH_PROPERTYPUTREF
通過引用設定屬性
參數pDispParams為參數資訊數組,元素類型為DISPPARAMS
typedef struct tagDISPPARAMS
{
VARIANTARG *rgvarg;//參數數組
DISPID *rgdispidNamedArgs;//參數中的DISPID數組
UINT cArgs;//數組中的參數個數
UINT cNamedArgs;//命名參數的個數
}DISPPARAMS;
注意:
如果是屬性設定,rgvarg數組中隻有一個參數,如果是方法調用,可以包含0到多個參數;
rgvarg數組中的VARIANTARG參數的vt域為VT_BYREF時,該參數可寫,否則為隻讀;
rgvarg數組中的VARIANTARG參數的vt域為VT_BYREF時,該參數可以作為輸出參數;
rgvarg數組中的VARIANTARG參數的vt域不為VT_BYREF時,參數内的字元串或者指針變量的所有權在客戶,客戶必須自己釋放資源,實作IDispatch接口的對象要想保留資料,則要拷貝資料或者調用指針變量的AddRef函數。
rgvarg數組中的VARIANTARG參數的vt域為VT_ERROR,并且scode域為DISP_E_PARAMNOTFOUND時,該參數可以作為可選參數,scode域作用是存放傳回的HRESULT。
<<COM原理與應用>>中曾經提到rgvarg數組中的參數存放順序和客戶程式調用時傳遞的參數順序剛好相反,我這裡對此表示懷疑。
關于命名參數的詳細讨論我在後面将談到,現在隻需要知道它可以不受參數次序的限制。
參數pVarResult儲存函數調用後的傳回資訊,因為Invoke已經将傳回值用于COM通用的HRESULT;
參數pExcepInfo傳回異常資訊;
參數puArgErr包含第一個産生錯誤的參數索引,當Invoke傳回的HRESULT值為DISP_E_TYPEMISMATCH或DISP_E_PARAM_NOTFOUND值時。
建立支援IDispatch接口的COM對象:
本節我們利用ATL7.1建立一個COM對象Baby。CBaby類從IDispatch接口派生,并且同時支援vtable方式。我們設想他有一個屬性Gender (性别),我們可以設定和擷取寶寶的性别屬性
現在我們先建立一個ATL項目IDspCOM。然後添加類CBaby,選擇接口類型為雙重。我們先在看一下生成的idl檔案中的接口定義:
[
object,
uuid(22C1BD80-2937-42FB-A7F8-5CEBD1257CB8),
dual,
nonextensible,
helpstring("IBaby 接口"),
pointer_default(unique)
]
interface IBaby : IDispatch
{
};
現在IBaby派生自IDispatch接口,除了繼承了IDispatch的4個方法和IUnknown的3個方法外,它現在還沒有任何自己的方法和屬性。ATL将幫我們實作前面的7個方法,我們無需關心。我們現在來建立自己的屬性Gender。
我們通過向導建立了該屬性,idl檔案如下:
interface IBaby : IDispatch{
[propget, id(1), helpstring("屬性 Gender")] HRESULT Gender([out, retval] BSTR* pVal);
[propput, id(1), helpstring("屬性 Gender")] HRESULT Gender([in] BSTR newVal);
};
我們可以看到其實屬性就是一對方法----設定和擷取屬性方法。兩個方法共用一個DISPID,值為1。在類的頭檔案中我們添加如下代碼:
public:
STDMETHOD(get_Gender)(BSTR* pVal);
STDMETHOD(put_Gender)(BSTR newVal);
private:
CComBSTR m_Gender;
在類的實作檔案中我們添加如下代碼:
STDMETHODIMP CBaby::get_Gender(BSTR* pVal)
{
m_Gender.CopyTo(pVal);
return S_OK;
}
STDMETHODIMP CBaby::put_Gender(BSTR newVal)
{
m_Gender=newVal;
return S_OK;
}
标準方式調用IDispatch接口的方法:
好了,我們現在來編寫客戶程式建立CBaby元件的執行個體并設定和擷取屬性。通常我們編寫c++程式調用com時需要導入元件dll檔案或者tlb檔案。但是由于IDispatch接口提供了統一的方式通路,是以我們可以不必非要這些檔案。現在我們建立Win32 Console程式。
我們需要CBaby類的CLSID才能夠建立該對象,我們可以通過ProgID來擷取相應的CLSID,ProgID是一個友好的名稱,用來标志一個元件實作類。通常建立ATL元件後,我們可以在工程中找到元件名.RGS腳本檔案,如下面的檔案baby.rgs:
HKCR //HKEY_CLASSES_ROOT的縮寫
{
IDspCOM.Baby.1 = s 'Baby Class'//s代表REG_SZ;d代表REG_DWORD;b代表REG_BINARY
{
CLSID = s '{79278E86-6551-40EB-9BB0-25655A1EE60D}'
}
IDspCOM.Baby = s 'Baby Class'
{
CLSID = s '{79278E86-6551-40EB-9BB0-25655A1EE60D}'
CurVer = s 'IDspCOM.Baby.1'
}
NoRemove CLSID //登出元件時不能删除CLSID關鍵字
{
ForceRemove {79278E86-6551-40EB-9BB0-25655A1EE60D} = s 'Baby Class' //寫該鍵時應該删除目前鍵和所有子健
{
ProgID = s 'IDspCOM.Baby.1'//有版本号的ProgID
VersionIndependentProgID = s 'IDspCOM.Baby'//無版本号的ProgID
ForceRemove 'Programmable'
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'Both'
}
val AppID = s '%APPID%'
'TypeLib' = s '{5B0732AF-E621-4E5A-A3EE-7F543CFB6701}'
}
}
}
WIN32 CONSOLE程式可以代碼如下:(使用Unicode編碼)
// IDspTest.cpp : 定義控制台應用程式的入口點。
//
#include "stdafx.h"
#include <iostream>
#include <atlstr.h>
#include <string>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
::CoInitialize(NULL);
//建立IDspCOM.Baby對象
CLSID Clsid;
::CLSIDFromProgID(L"IDspCOM.Baby",&Clsid);
IDispatch* pIDsp=NULL;
HRESULT hr=::CoCreateInstance(Clsid,NULL,CLSCTX_ALL,IID_IDispatch,(void**)&pIDsp);
if(FAILED(hr))
{
cout<<"Failed To Create IDspCOM.Babyj Object"<<endl;
::CoUninitialize();
return 0;
}
//檢查IDspCOM.Baby對象是否支援類型資訊
UINT Count;
pIDsp->GetTypeInfoCount(&Count);
if(Count==0)
{
cout<<"IDspCOM.Babyj Object has not TypeInfo"<<endl;
return 0;
}
//擷取屬性ID
DISPID PropertyID;
BSTR PropName[1];//BSTR可以在這裡作為OLECHAR*使用的前提是我們必須保證BSTR字元串中不内嵌NULL字元
PropName[0]=SysAllocString(L"Gender");
hr=pIDsp->GetIDsOfNames(IID_NULL,PropName,1,LOCALE_SYSTEM_DEFAULT,&PropertyID);
SysFreeString(PropName[0]);
//設定屬性Gender值為男
DISPPARAMS Params;
Params.cArgs=1;
Params.cNamedArgs=1;//必須,原因不明
DISPID dispidPut = DISPID_PROPERTYPUT;//必須,原因不明
Params.rgdispidNamedArgs=&dispidPut;//必須,原因不明
Params.rgvarg=new VARIANTARG[1];
Params.rgvarg[0].vt=VT_BSTR;
Params.rgvarg[0].bstrVal=SysAllocString(L"男");
CComVariant Result;
EXCEPINFO Info;
UINT ArgErr;
hr=pIDsp->Invoke(PropertyID,IID_NULL,GetUserDefaultLCID(),DISPATCH_PROPERTYPUT,&Params,&Result,&Info,&ArgErr);
VariantClear(Params.rgvarg);
delete Params.rgvarg;
//擷取屬性Gender值
DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};
hr=pIDsp->Invoke(PropertyID,IID_NULL,GetUserDefaultLCID(),DISPATCH_PROPERTYGET,&dispparamsNoArgs,&Result,&Info,&ArgErr);
USES_CONVERSION;
cout<<W2A(Result.bstrVal)<<endl;
//釋放接口
pIDsp->Release();
::CoUninitialize();
return 0;
}
采用ATL智能指針類調用IDispatch接口的方法:
采用标準方法調用IDispatch非常繁瑣,而且容易出錯,為了簡化這些過程,ATL7.1類庫中提供了一個智能指針類CComDispatchDriver類。定義如下:
typedef CComQIPtr<IDispatch, &__uuidof(IDispatch)> CComDispatchDriver;
實質上它是CComQIPtr類的特化版本。源代碼如下:
//specialization for IDispatch
template <>
class CComPtr<IDispatch> : public CComPtrBase<IDispatch>
{
public:
CComPtr() throw()
{
}
CComPtr(IDispatch* lp) throw() :
CComPtrBase<IDispatch>(lp)
{
}
CComPtr(const CComPtr<IDispatch>& lp) throw() :
CComPtrBase<IDispatch>(lp.p)
{
}
IDispatch* operator=(IDispatch* lp) throw()
{
return static_cast<IDispatch*>(AtlComPtrAssign((IUnknown**)&p, lp));
}
IDispatch* operator=(const CComPtr<IDispatch>& lp) throw()
{
return static_cast<IDispatch*>(AtlComPtrAssign((IUnknown**)&p, lp.p));
}
// IDispatch specific stuff
HRESULT GetPropertyByName(LPCOLESTR lpsz, VARIANT* pVar) throw()
{
ATLASSERT(p);
ATLASSERT(pVar);
DISPID dwDispID;
HRESULT hr = GetIDOfName(lpsz, &dwDispID);
if (SUCCEEDED(hr))
hr = GetProperty(dwDispID, pVar);
return hr;
}
HRESULT GetProperty(DISPID dwDispID, VARIANT* pVar) throw()
{
return GetProperty(p, dwDispID, pVar);
}
HRESULT PutPropertyByName(LPCOLESTR lpsz, VARIANT* pVar) throw()
{
ATLASSERT(p);
ATLASSERT(pVar);
DISPID dwDispID;
HRESULT hr = GetIDOfName(lpsz, &dwDispID);
if (SUCCEEDED(hr))
hr = PutProperty(dwDispID, pVar);
return hr;
}
HRESULT PutProperty(DISPID dwDispID, VARIANT* pVar) throw()
{
return PutProperty(p, dwDispID, pVar);
}
HRESULT GetIDOfName(LPCOLESTR lpsz, DISPID* pdispid) throw()
{
return p->GetIDsOfNames(IID_NULL, const_cast<LPOLESTR*>(&lpsz), 1, LOCALE_USER_DEFAULT, pdispid);
}
// Invoke a method by DISPID with no parameters
HRESULT Invoke0(DISPID dispid, VARIANT* pvarRet = NULL) throw()
{
DISPPARAMS dispparams = { NULL, NULL, 0, 0};
return p->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispparams, pvarRet, NULL, NULL);
}
// Invoke a method by name with no parameters
HRESULT Invoke0(LPCOLESTR lpszName, VARIANT* pvarRet = NULL) throw()
{
HRESULT hr;
DISPID dispid;
hr = GetIDOfName(lpszName, &dispid);
if (SUCCEEDED(hr))
hr = Invoke0(dispid, pvarRet);
return hr;
}
// Invoke a method by DISPID with a single parameter
HRESULT Invoke1(DISPID dispid, VARIANT* pvarParam1, VARIANT* pvarRet = NULL) throw()
{
DISPPARAMS dispparams = { pvarParam1, NULL, 1, 0};
return p->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispparams, pvarRet, NULL, NULL);
}
// Invoke a method by name with a single parameter
HRESULT Invoke1(LPCOLESTR lpszName, VARIANT* pvarParam1, VARIANT* pvarRet = NULL) throw()
{
HRESULT hr;
DISPID dispid;
hr = GetIDOfName(lpszName, &dispid);
if (SUCCEEDED(hr))
hr = Invoke1(dispid, pvarParam1, pvarRet);
return hr;
}
// Invoke a method by DISPID with two parameters
HRESULT Invoke2(DISPID dispid, VARIANT* pvarParam1, VARIANT* pvarParam2, VARIANT* pvarRet = NULL) throw();
// Invoke a method by name with two parameters
HRESULT Invoke2(LPCOLESTR lpszName, VARIANT* pvarParam1, VARIANT* pvarParam2, VARIANT* pvarRet = NULL) throw()
{
HRESULT hr;
DISPID dispid;
hr = GetIDOfName(lpszName, &dispid);
if (SUCCEEDED(hr))
hr = Invoke2(dispid, pvarParam1, pvarParam2, pvarRet);
return hr;
}
// Invoke a method by DISPID with N parameters
HRESULT InvokeN(DISPID dispid, VARIANT* pvarParams, int nParams, VARIANT* pvarRet = NULL) throw()
{
DISPPARAMS dispparams = { pvarParams, NULL, nParams, 0};
return p->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispparams, pvarRet, NULL, NULL);
}
// Invoke a method by name with Nparameters
HRESULT InvokeN(LPCOLESTR lpszName, VARIANT* pvarParams, int nParams, VARIANT* pvarRet = NULL) throw()
{
HRESULT hr;
DISPID dispid;
hr = GetIDOfName(lpszName, &dispid);
if (SUCCEEDED(hr))
hr = InvokeN(dispid, pvarParams, nParams, pvarRet);
return hr;
}
static HRESULT PutProperty(IDispatch* p, DISPID dwDispID, VARIANT* pVar) throw()
{
ATLASSERT(p);
ATLASSERT(pVar != NULL);
if (pVar == NULL)
return E_POINTER;
if(p == NULL)
return E_INVALIDARG;
ATLTRACE(atlTraceCOM, 2, _T("CPropertyHelper::PutProperty/n"));
DISPPARAMS dispparams = {NULL, NULL, 1, 1};
dispparams.rgvarg = pVar;
DISPID dispidPut = DISPID_PROPERTYPUT;
dispparams.rgdispidNamedArgs = &dispidPut;
if (pVar->vt == VT_UNKNOWN || pVar->vt == VT_DISPATCH ||
(pVar->vt & VT_ARRAY) || (pVar->vt & VT_BYREF))
{
HRESULT hr = p->Invoke(dwDispID, IID_NULL,
LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUTREF,
&dispparams, NULL, NULL, NULL);
if (SUCCEEDED(hr))
return hr;
}
return p->Invoke(dwDispID, IID_NULL,
LOCALE_USER_DEFAULT, DISPATCH_PROPERTYPUT,
&dispparams, NULL, NULL, NULL);
}
static HRESULT GetProperty(IDispatch* p, DISPID dwDispID, VARIANT* pVar) throw()
{
ATLASSERT(p);
ATLASSERT(pVar != NULL);
if (pVar == NULL)
return E_POINTER;
if(p == NULL)
return E_INVALIDARG;
ATLTRACE(atlTraceCOM, 2, _T("CPropertyHelper::GetProperty/n"));
DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};
return p->Invoke(dwDispID, IID_NULL,
LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET,
&dispparamsNoArgs, pVar, NULL, NULL);
}
};
下面我們的客戶程式将使用該類完成前一節的屬性設定和擷取功能:
int _tmain(int argc, _TCHAR* argv[])
{
::CoInitialize(NULL);
CComDispatchDriver Dsp;
Dsp.CoCreateInstance(L"IDspCOM.Baby");
CComVariant Value(L"女");
Dsp.PutPropertyByName(L"Gender",&Value);
CComVariant Value2;
Dsp.GetPropertyByName(L"Gender",&Value2);
::CoUninitialize();
return 0;
}
使用類COleDispatchDriver調用IDispatch的方法:
COleDispatchDriver類是MFC類庫中用于操縱IDispatch接口的類。下面是該類的聲明:
class COleDispatchDriver
{
// Constructors
public:
COleDispatchDriver();
COleDispatchDriver(LPDISPATCH lpDispatch, BOOL bAutoRelease = TRUE);
COleDispatchDriver(const COleDispatchDriver& dispatchSrc);
// Attributes
LPDISPATCH m_lpDispatch;
BOOL m_bAutoRelease;
// Operations
BOOL CreateDispatch(REFCLSID clsid, COleException* pError = NULL);
BOOL CreateDispatch(LPCTSTR lpszProgID, COleException* pError = NULL);
void AttachDispatch(LPDISPATCH lpDispatch, BOOL bAutoRelease = TRUE);
LPDISPATCH DetachDispatch();
// detach and get ownership of m_lpDispatch
void ReleaseDispatch();
// helpers for IDispatch::Invoke
void AFX_CDECL InvokeHelper(DISPID dwDispID, WORD wFlags,
VARTYPE vtRet, void* pvRet, const BYTE* pbParamInfo, ...);
void AFX_CDECL SetProperty(DISPID dwDispID, VARTYPE vtProp, ...);
void GetProperty(DISPID dwDispID, VARTYPE vtProp, void* pvProp) const;
// special operators
operator LPDISPATCH();
const COleDispatchDriver& operator=(const COleDispatchDriver& dispatchSrc);
// Implementation
public:
~COleDispatchDriver();
void InvokeHelperV(DISPID dwDispID, WORD wFlags, VARTYPE vtRet,
void* pvRet, const BYTE* pbParamInfo, va_list argList);
};
先建立Win32 Console程式,選擇支援MFC,然後編寫如下代碼:
::CoInitialize(NULL);
COleDispatchDriver Dsp;
if(!Dsp.CreateDispatch(L"IDspCOM.Baby"))
{
cout<<L"建立失敗"<<endl;
}
DISPID PropID;
BSTR PropName[1];//BSTR可以在這裡作為OLECHAR*使用的前提是我們必須保證BSTR字元串中不内嵌NULL字元
PropName[0]=SysAllocString(L"Gender");
Dsp.m_lpDispatch->GetIDsOfNames(IID_NULL,PropName,1,LOCALE_SYSTEM_DEFAULT,&PropID);
Dsp.SetProperty(PropID,VT_BSTR,L"男");
CString Value;
Dsp.GetProperty(PropID,VT_BSTR,&Value);
Dsp.ReleaseDispatch;
::CoUninitialize();
很明顯,該類使用比CComDispatchDriver類要繁瑣的多,是以不推薦使用。不過MapObjects中的C++類采用派生自該類,同時為了便于使用增加了很多成員函數的技術
本文來自CSDN部落格,轉載請标明出處:http://blog.csdn.net/sheismylife/archive/2005/08/23/463031.aspx