IDispatch接口和原理.docx
- 文档编号:12221588
- 上传时间:2023-04-17
- 格式:DOCX
- 页数:17
- 大小:23.03KB
IDispatch接口和原理.docx
《IDispatch接口和原理.docx》由会员分享,可在线阅读,更多相关《IDispatch接口和原理.docx(17页珍藏版)》请在冰豆网上搜索。
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:
publicIUnknown
{
public:
virtualHRESULTSTDMETHODCALLTYPEGetTypeInfoCount(
/*[out]*/UINT*pctinfo)=0;
virtualHRESULTSTDMETHODCALLTYPEGetTypeInfo(
/*[in]*/UINTiTInfo,
/*[in]*/LCIDlcid,
/*[out]*/ITypeInfo**ppTInfo)=0;
virtualHRESULTSTDMETHODCALLTYPEGetIDsOfNames(
/*[in]*/REFIIDriid,
/*[size_is][in]*/LPOLESTR*rgszNames,
/*[in]*/UINTcNames,
/*[in]*/LCIDlcid,
/*[size_is][out]*/DISPID*rgDispId)=0;
virtual/*[local]*/HRESULTSTDMETHODCALLTYPEInvoke(
/*[in]*/DISPIDdispIdMember,
/*[in]*/REFIIDriid,
/*[in]*/LCIDlcid,
/*[in]*/WORDwFlags,
/*[out][in]*/DISPPARAMS*pDispParams,
/*[out]*/VARIANT*pVarResult,
/*[out]*/EXCEPINFO*pExcepInfo,
/*[out]*/UINT*puArgErr)=0;
};
我们通过IDispatch的GUID到注册表中搜索,可以搜索到如下结果:
注意在IDispatch接口GUID下面还有两个展开的GUID项,他们分别是ITypeInfo和ITypeLib接口。
这两个接口在自动化应用中也是非常重要的。
今后我们会经常看到他们。
IDispatch接口方法简介:
1)HRESULTSTDMETHODCALLTYPEGetTypeInfoCount(
/*[out]*/UINT*pctinfo);
判断实现了IDispatch接口的对象是否支持类型信息,如果返回1则支持,返回0则不支持。
2)HRESULTSTDMETHODCALLTYPEGetTypeInfo(
/*[in]*/UINTiTInfo,
/*[in]*/LCIDlcid,
/*[out]*/ITypeInfo**ppTInfo)=0;
获取对象的类型信息接口指针,该方法调用之前总应该先调用方法GetTypeInfoCount(…)确认是否支持类型信息。
参数iTInfo必须为0,否则该方法将返回DISP_E_BADINDEX表示失败
参数lcid传递类型信息的地域标志。
IDispatch接口的方法和属性在不同的语言环境(地域标志)可以使用不同的名称,因而lcid不同可能会导致返回的ITypeInfo接口指针不同。
如果我们创建的组件根据不同的地域标志对属性和方法起不同的名字,我们就要使用这个参数,否则可以忽略。
3)HRESULTSTDMETHODCALLTYPEGetIDsOfNames(
/*[in]*/REFIIDriid,
/*[size_is][in]*/LPOLESTR*rgszNames,
/*[in]*/UINTcNames,
/*[in]*/LCIDlcid,
/*[size_is][out]*/DISPID*rgDispId)
IDispatch接口的属性实质上是方法,方法也就是成员函数,IDispatch接口把所有成员函数的入口地址放入到一个数组中,并且内部组织了一个Map,将数组索引和方法名称一一映射。
我们常见的DISPID就是这些方法在数组中的索引。
如果我们想调用某一个方法,我们就需要DISPID来让我们找到该方法的地址。
参数riid必须为NULL。
参数rgszNames为字符串数组,第一个字符串为方法或者属性的名称,后续的字符串为参数名称,IDispatch接口的参数也可以有名字。
参数cNames指定rgszNames数组中字符串的个数。
参数lcid传递地域标志,同GetTypeInfo方法中的参数。
参数rgDispId输出一个数组,每个数组成员对应rgszNames中的一个字符串名称。
关于DISPID的进一步说明:
typedefLONGDISPID;
typedefDISPIDMEMBERID;
DISPID小于等于0的值都是有特殊意义的,如下面介绍的----
/*DISPIDreservedtoindicatean"unknown"name*/
/*onlyreservedfordatamembers(properties);reusedasamethoddispidbelow*/
//如果GetIDsOfNames函数找不到与名称相对应的DISPID,返回该值
#defineDISPID_UNKNOWN(-1)
/*DISPIDreservedforthe"value"property*/
//如果调用时不指定方法或者属性,则使用该缺省值
#defineDISPID_VALUE(0)
/*ThefollowingDISPIDisreservedtoindicatetheparam
*thatistheright-hand-side(or"put"value)ofaPropertyPut
*/
//表明属性设置函数中某一个参数将接受新属性值
#defineDISPID_PROPERTYPUT(-3)
/*DISPIDreservedforthestandard"NewEnum"method*/
//用于集合对象
#defineDISPID_NEWENUM(-4)
/*DISPIDreservedforthestandard"Evaluate"method,脚本语言中可以用[]调用该方法*/
#defineDISPID_EVALUATE(-5)
/*表示某方法具有和构造函数相同的功能*/
#defineDISPID_CONSTRUCTOR(-6)
/*表示某方法具有和析构函数相同的功能*/
#defineDISPID_DESTRUCTOR(-7)
/*TheCollectproperty.YouusethispropertyifthemethodyouarecallingthroughInvokeisanaccessorfunction.*/
#defineDISPID_COLLECT(-8)
/*Therange-500through-999isreservedforControls*/
/*Therange0x80010000through0x8001FFFFisreservedforControls*/
/*Therange-5000through-5499isreservedforActiveXAccessability*/
/*Therange-2000through-2499isreservedforVB5*/
/*Therange-3900through-3999isreservedforForms*/
/*Therange-5500through-5550isreservedforForms*/
/*TheremainderofthenegativeDISPIDsarereservedforfutureuse*/
4)HRESULTSTDMETHODCALLTYPEInvoke(
/*[in]*/DISPIDdispIdMember,
/*[in]*/REFIIDriid,
/*[in]*/LCIDlcid,
/*[in]*/WORDwFlags,
/*[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
typedefstructtagDISPPARAMS
{
/*[size_is]*/VARIANTARG*rgvarg;//参数数组
/*[size_is]*/DISPID*rgdispidNamedArgs;//参数中的DISPID数组
UINTcArgs;//数组中的参数个数
UINTcNamedArgs;//命名参数的个数
}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)
]
interfaceIBaby:
IDispatch
{
};
现在IBaby派生自IDispatch接口,除了继承了IDispatch的4个方法和IUnknown的3个方法外,它现在还没有任何自己的方法和属性。
ATL将帮我们实现前面的7个方法,我们无需关心。
我们现在来创建自己的属性Gender。
我们通过向导创建了该属性,idl文件如下:
interfaceIBaby:
IDispatch{
[propget,id
(1),helpstring("属性Gender")]HRESULTGender([out,retval]BSTR*pVal);
[propput,id
(1),helpstring("属性Gender")]HRESULTGender([in]BSTRnewVal);
};
我们可以看到其实属性就是一对方法----设置和获取属性方法。
两个方法共用一个DISPID,值为1。
在类的头文件中我们添加如下代码:
public:
STDMETHOD(get_Gender)(BSTR*pVal);
STDMETHOD(put_Gender)(BSTRnewVal);
private:
CComBSTRm_Gender;
在类的实现文件中我们添加如下代码:
STDMETHODIMPCBaby:
:
get_Gender(BSTR*pVal)
{
m_Gender.CopyTo(pVal);
returnS_OK;
}
STDMETHODIMPCBaby:
:
put_Gender(BSTRnewVal)
{
m_Gender=newVal;
returnS_OK;
}
标准方式调用IDispatch接口的方法:
好了,我们现在来编写客户程序创建CBaby组件的实例并设置和获取属性。
通常我们编写c++程序调用com时需要导入组件dll文件或者tlb文件。
但是由于IDispatch接口提供了统一的方式访问,所以我们可以不必非要这些文件。
现在我们创建Win32Console程序。
我们需要CBaby类的CLSID才能够创建该对象,我们可以通过ProgID来获取相应的CLSID,ProgID是一个友好的名称,用来标志一个组件实现类。
通常创建ATL组件后,我们可以在工程中找到组件名.RGS脚本文件,如下面的文件baby.rgs:
HKCR//HKEY_CLASSES_ROOT的缩写
{
IDspCOM.Baby.1=s'BabyClass'//s代表REG_SZ;d代表REG_DWORD;b代表REG_BINARY
{
CLSID=s'{79278E86-6551-40EB-9BB0-25655A1EE60D}'
}
IDspCOM.Baby=s'BabyClass'
{
CLSID=s'{79278E86-6551-40EB-9BB0-25655A1EE60D}'
CurVer=s'IDspCOM.Baby.1'
}
NoRemoveCLSID//注销组件时不能删除CLSID关键字
{
ForceRemove{79278E86-6551-40EB-9BB0-25655A1EE60D}=s'BabyClass'//写该键时应该删除当前键和所有子健
{
ProgID=s'IDspCOM.Baby.1'//有版本号的ProgID
VersionIndependentProgID=s'IDspCOM.Baby'//无版本号的ProgID
ForceRemove'Programmable'
InprocServer32=s'%MODULE%'
{
valThreadingModel=s'Both'
}
valAppID=s'%APPID%'
'TypeLib'=s'{5B0732AF-E621-4E5A-A3EE-7F543CFB6701}'
}
}
}
WIN32CONSOLE程序可以代码如下:
(使用Unicode编码)
//IDspTest.cpp:
定义控制台应用程序的入口点。
//
#include"stdafx.h"
#include
#include
#include
usingnamespacestd;
int_tmain(intargc,_TCHAR*argv[])
{
:
:
CoInitialize(NULL);
//创建IDspCOM.Baby对象
CLSIDClsid;
:
:
CLSIDFromProgID(L"IDspCOM.Baby",&Clsid);
IDispatch*pIDsp=NULL;
HRESULThr=:
:
CoCreateInstance(Clsid,NULL,CLSCTX_ALL,IID_IDispatch,(void**)&pIDsp);
if(FAILED(hr))
{
cout<<"FailedToCreateIDspCOM.BabyjObject"< : : CoUninitialize(); return0; } //检查IDspCOM.Baby对象是否支持类型信息 UINTCount; pIDsp->GetTypeInfoCount(&Count); if(Count==0) { cout<<"IDspCOM.BabyjObjecthasnotTypeInfo"< return0; } //获取属性ID DISPIDPropertyID; BSTRPropName[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值为男 DISPPARAMSParams; Params.cArgs=1; Params.cNamedArgs=1;//必须,原因不明 DISPIDdispidPut=DISPID_PROPERTYPUT;//必须,原因不明 Params.rgdispidNamedArgs=&dispidPut;//必须,原因不明 Params.rgvarg=newVARIANTARG[1]; Params.rgvarg[0].vt=VT_BSTR; Params.rgvarg[0].bstrVal=SysAllocString(L"男"); CComVariantResult; EXCEPINFOInfo; UINTArgErr; hr=pIDsp->Invoke(PropertyID,IID_NULL,GetUserDefaultLCID(),DISPATCH_PROPERTYPUT,&Params,&Result,&Info,&ArgErr); VariantClear(Params.rgvarg); deleteParams.rgvarg; //获取属性Gender值 DISPPARAMSdispparamsNoArgs={NULL,NULL,0,0}; hr=pIDsp->Invoke(PropertyID,IID_NULL,GetUserDefaultLCID(),DISPATCH_PROPERTYGET,&dispparamsNoArgs,&Result,&Info,&ArgErr); USES_CONVERSION; cout< //释放接口 pIDsp->Release(); : : CoUninitialize(); return0; } 采用ATL智能指针类调用IDispatch接口的方法
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- IDispatch 接口 原理