
IDispatch接口原理与应用
目录:
前言:
IDispatch接口的定义:
创建支持IDispatch接口的COM对象:
标准方式调用IDispatch接口的方法:
采用ATL智能指针类调用IDispatch接口的方法:
使用类COleDispatchDriver调用IDispatch的方法:
前言:
尽管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(
/* [out] */ UINT *pctinfo) = 0;
virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(
/* [in] */ UINT iTInfo,
/* [in] */ LCID lcid,
/* [out] */ ITypeInfo **ppTInfo) = 0;
virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(
/* [in] */ REFIID riid,
/* [size_is][in] */ LPOLESTR *rgszNames,
/* [in] */ UINT cNames,
/* [in] */ LCID lcid,
/* [size_is][out] */ DISPID *rgDispId) = 0;
virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke(
/* [in] */ DISPID dispIdMember,
/* [in] */ REFIID riid,
/* [in] */ LCID lcid,
/* [in] */ WORD wFlags,
/* [out][in] */ DISPPARAMS *pDispParams,
/* [out] */ VARIANT *pVarResult,
/* [out] */ EXCEPINFO *pExcepInfo,
/* [out] */ UINT *puArgErr) = 0;
};
我们通过IDispatch的GUID到注册表中搜索,可以搜索到如下结果:
注意在IDispatch接口GUID下面还有两个展开的GUID项,他们分别是ITypeInfo和ITypeLib接口。这两个接口在自动化应用中也是非常重要的。今后我们会经常看到他们。
IDispatch接口方法简介:
1)HRESULT STDMETHODCALLTYPE GetTypeInfoCount(
/* [out] */ UINT *pctinfo) ;
判断实现了IDispatch接口的对象是否支持类型信息,如果返回1则支持,返回0则不支持。
2) HRESULT STDMETHODCALLTYPE GetTypeInfo(
/* [in] */ UINT iTInfo,
/* [in] */ LCID lcid,
/* [out] */ ITyp
eInfo **ppTInfo) = 0;
获取对象的类型信息接口指针,该方法调用之前总应该先调用方法GetTypeInfoCount(…)确认是否支持类型信息。
参数iTInfo必须为0,否则该方法将返回DISP_E_BADINDEX表示失败
参数lcid传递类型信息的地域标志。IDispatch接口的方法和属性在不同的语言环境(地域标志)可以使用不同的名称,因而lcid不同可能会导致返回的ITypeInfo接口指针不同。如果我们创建的组件根据不同的地域标志对属性和方法起不同的名字,我们就要使用这个参数,否则可以忽略。
3)HRESULT STDMETHODCALLTYPE GetIDsOfNames(
/* [in] */ REFIID riid,
/* [size_is][in] */ LPOLESTR *rgszNames,
/* [in] */ UINT cNames,
/* [in] */ LCID lcid,
/* [size_is][out] */ 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的值都是有特殊意义的,如下面介绍的----
/* DISPID reserved to indicate an "unknown" name */
/* only reserved for data members (properties); reused as a method dispid below */
//如果GetIDsOfNames函数找不到与名称相对应的DISPID,返回该值
#define DISPID_UNKNOWN ( -1 )
/* DISPID reserved for the "value" property */
//如果调用时不指定方法或者属性,则使用该缺省值
#define DISPID_VALUE ( 0 )
/* The following DISPID is reserved to indicate the param
* that is the right-hand-side (or "put" value) of a PropertyPut
*/
//表明属性设置函数中某一个参数将接受新属性值
#define DISPID_PROPERTYPUT ( -3 )
/* DISPID reserved for the standard "NewEnum" method */
//用于集合对象
#define DISPID_NEWENUM ( -4 )
/* DISPID reserved for the standard "Evaluate" method,脚本语言中可以用[]调用该方法 */
#define DISPID_EVALUATE ( -5 )
/* 表示某方法具有和构造函数相同的功能*/
#define DISPID_CONSTRUCTOR ( -6 )
/* 表示某方法具有和析
构函数相同的功能*/
#define DISPID_DESTRUCTOR ( -7 )
/* The Collect property. You use this property if the method you are calling through Invoke is an accessor function.*/
#define DISPID_COLLECT ( -8 )
/* The range -500 through -999 is reserved for Controls */
/* The range 0x80010000 through 0x8001FFFF is reserved for Controls */
/* The range -5000 through -5499 is reserved for ActiveX Accessability */
/* The range -2000 through -2499 is reserved for VB5 */
/* The range -3900 through -3999 is reserved for Forms */
/* The range -5500 through -5550 is reserved for Forms */
/* The remainder of the negative DISPIDs are reserved for future use */
4) HRESULT STDMETHODCALLTYPE Invoke(
/* [in] */ DISPID dispIdMember,
/* [in] */ REFIID riid,
/* [in] */ LCID lcid,
/* [in] */ WORD wFlags,
/* [out][in] */ DISPPARAMS *pDispParams,
/* [out] */ VARIANT *pVarResult,
/* [out] */ EXCEPINFO *pExcepInfo,
/* [out] */ 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
{
/* [size_is] */ VARIANTARG *rgvarg;//参数数组
/* [size_is] */ 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。
< 关于命名参数的详细讨论我在后面将谈到, 现在只需要知道它可以不受参数次序的。 参数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-25655 A1EE60D}' } 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 #include #include using namespace std; int _tmain(int argc, _TCHAR* argv[]) { ::CoInitialize(NULL); //创建IDspCOM.Baby对象 CLSID Clsid; ::CLSIDFromProgID(L"IDspCOM.Baby ndl; //释放接口 pIDsp->Release(); ::CoUninitialize(); return 0; } 采用ATL智能指针类调用IDispatch接口的方法: 采用标准方法调用IDispatch非常繁琐,而且容易出错,为了简化这些过程,ATL7.1类库中提供了一个智能指针类CComDispatchDriver类。定义如下: typedef CComQIPtr 实质上它是CComQIPtr类的特化版本。源代码如下: //specialization for IDispatch template <> class CComPtr { public: CComPtr() throw() { } CComPtr(IDispatch* lp) throw() : CComPtrBase { } CComPtr(const CComPtr CComPtrBase { } IDispatch* operator=(IDispatch* lp) throw() { return static_cast } IDispatch* operator=(const CComPtr { return static_cast } // 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 } // 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_PROPERTYPU PUT, &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 TR字符串中不内嵌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++类采用派生自该类,同时为了便于使用增加了很多成员函数的技术。
