分享
 
 
 

COM 组件设计与应用(十一)—— IDispatch 及双接口的调用

王朝vc·作者佚名  2006-01-17
窄屏简体版  字體: |||超大  

COM 组件设计与应用(十一)

IDispatch 及双接口的调用

作者:杨老师

下载源代码

一、前言

前段时间,由于工作比较忙,没有能及时地写作。其间收到了很多网友的来信询问和鼓励,在此一并表示感谢。咳......我也需要工作来养家糊口呀......

上回书介绍了两种方法来写自动化(IDispatch)接口的组件程序,一是用

MFC 方式编写“纯粹”的 IDispatch 接口;二是用 ATL 方式编写“双接口”的组件。

二、IDispatch 接口和双接口

使用者要想调用普通的 COM 组件功能,必须要加载这个组件的类型库(Type library)文件

tlb(比如在 VC 中使用 #import)。然而,在脚本程序中,由于脚本是被解释执行的,所以无法使用加载类型库的方式进行预编译。那么脚本解释器如何使用

COM 组件那?这就是自动化(IDispatch)组件大显身手的地方了。IDispatch

接口需要实现4个函数,调用者只通过这4个函数,就能实现调用自动化组件中所有的函数。这4个函数功能如下:

HRESULT GetTypeInfoCount(

[out] UINT * pctinfo)

组件中提供几个类型库?当然一般都是一个啦。

但如果你在一个组件中实现了多个 IDispatch 接口,那就不一定啦(注1)

HRESULT GetTypeInfo(

[in] UINT iTInfo,

[in] LCID lcid,

[out] ITypeInfo ** ppTInfo)

调用者通过该函数取得他想要的类型库。

幸好,在 99% 的情况下,我们都不用关心这两个函数的实现,因为 MFC/ATL

都帮我们完成了默认的一个实现,如果是自己完成函数代码,甚至可以直接返回 E_NOTIMPL 表示没有实现。(注2)

HRESULT GetIDsOfNames(

[in] REFIID riid,

[in,size_is(cNames)] LPOLESTR * rgszNames,

[in] UINT cNames,

[in] LCID lcid,

[out,size_is(cNames)] DISPID * rgDispId)

根据函数名称取得函数序号,为调用 Invoke()

做准备。

所谓函数序号,大家去观察双接口 IDL 文件和 MFC 的 ODL 文件,每一个函数和属性都会有 [id(序号)....]

这样的描述。

HRESULT Invoke(

[in] DISPID dispIdMember,

[in] REFIID riid,

[in] LCID lcid,

[in] WORD wFlags,

[in,out] DISPPARAMS * pDispParams,

[out] VARIANT * pVarResult,

[out] EXCEPINFO * pExcepInfo,

[out] UINT * puArgErr)

根据序号,执行函数。

使用 MFC/ATL 写的组件程序,我们也不必关心这个函数的实现。如果是自己写代码,则该函数类似如下实现:

switch(dispIdMember)

{

case 1: .....; break;

case 2: .....; break;

....

}

其实,就是根据序号进行分支调用啦。(注3)

从 Invoke()

函数的实现就可以看出,使用 IDispatch 接口的程序,其执行效率是比较低的。ATL

从效率出发,实现了一种叫“双接口(dual)”的接口模式。下面我们来看看,到底什么是双接口:

图一、双接口(dual) 结构示意图

从上图中可以看出,所谓双接口,其实是在一个 VTAB 的虚函数表中容纳了三个接口(因为任何接口都是从

IUnknown 派生的,所以就不强调 IUnknown

了,叫做双接口)。我们如果从任意一个接口中调用 QueryInterface()得到另外的接口指针的话,其实,得到的指针地址都是同一个。双接口有什么好处那?答:好呀,多好呀,特别好呀......

使用方式

因为

所以

脚本语言使用组件

解释器只认识 IDispatch

接口

可以调用,但执行效率最低

编译型语言使用组件

它认识 IDispatch

接口

可以调用,执行效率比较低

编译型语言使用组件

它装载类型库后,就认识了 Ixxx

接口

可以直接调用 Ixxx

函数,效率最高啦

结论

双接口,既满足脚本语言的使用方便性,又满足编译型语言的使用高效性。

于是,我们写的所有的 COM 组件接口,都用双接口实现吗?

错!否!NO!

如果不是明确非要支持脚本的调用,则最好不要使用双接口,因为:

如果所有函数都放在一个双接口中,那么层次、结构、分类不清

如果使用多个双接口,则会产生其它问题(注4)

双接口、IDispatch接口只支持自动化的参数类型,使用受到限制,某些情况下很不方便喽

还有很多弊病呦,不过现在我想不起来喽......

三、使用方法

如果你的开发环境是 vc6.0,那么我们使用第九回中的Simple6组件为例,快去下载呀......

如果你的开发环境是 vc.net 2003,那么用第十回中的Simple8组件为例,快去下载呀......

嘿嘿,其实不下载也没有关系,因为你只要下载本回的示例程序,里面已经包含了所需的组件。但使用前不要忘了去注册呀:regsvr32.exe

simple6.dll 或 regsvr32.exe simple8.dll

(注意别忘了输入组件的安装目录)。注册成功后,就可以使用了,使用方法有:

示例程序

自动化组件的使用方式

简要说明

示例0

在脚本中调用

第九回/第十回中,已经做了介绍

示例1

使用 API 方式调用

揭示 IDispatch

的调用原理,但傻子才去这么使用那,会累死了

示例2

使用

CComDispatchDriver

的智能指针包装类

比直接使用 API 方式要简单多啦,这个不错!

示例3

使用 MFC 装载类型库的包装方式

简单!好用!常用!但它本质上是使用 IDispatch

接口,所以执行效率稍差

示例4

使用 #import

方式加载类型库方式

#import 方式使用组件,咱们在第七回中讲过啦。常用!对双接口组件,直接调用自定义接口函数,不再经过

IDispatch,因此执行效率最高啦

示例x

vb、java、c#、bcb、delphi.......

反正我不会,自己去请教高人去吧 :-(

示例一、IDispatch 调用原理篇void demo()

{

::CoInitialize( NULL );// COM 初始化

CLSID clsid;// 通过 ProgID 得到 CLSID

HRESULT hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &clsid );

ASSERT( SUCCEEDED( hr ) );// 如果失败,说明没有注册组件

IDispatch * pDisp = NULL;// 由 CLSID 启动组件,并得到 IDispatch 指针

hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IDispatch, (LPVOID *)&pDisp );

ASSERT( SUCCEEDED( hr ) );// 如果失败,说明没有初始化 COM

LPOLESTR pwFunName = L"Add";// 准备取得 Add 函数的序号 DispID

DISPID dispID;// 取得的序号,准备保存到这里

hr = pDisp-GetIDsOfNames(// 根据函数名,取得序号的函数

IID_NULL,

&pwFunName,// 函数名称的数组

1,// 函数名称数组中的元素个数

LOCALE_SYSTEM_DEFAULT,// 使用系统默认的语言环境

&dispID );// 返回值

ASSERT( SUCCEEDED( hr ) );// 如果失败,说明组件根本就没有 ADD 函数

VARIANTARG v[2];// 调用 Add(1,2) 函数所需要的参数

v[0].vt = VT_I4;v[0].lVal = 2;// 第二个参数,整数2

v[1].vt = VT_I4;v[1].lVal = 1;// 第一个参数,整数1

DISPPARAMS dispParams = { v, NULL, 2, 0 };// 把参数包装在这个结构中

VARIANT vResult;// 函数返回的计算结果

hr = pDisp-Invoke(// 调用函数

dispID,// 函数由 dispID 指定

IID_NULL,

LOCALE_SYSTEM_DEFAULT,// 使用系统默认的语言环境

DISPATCH_METHOD,// 调用的是方法,不是属性

&dispParams,// 参数

&vResult,// 返回值

NULL,// 不考虑异常处理

NULL);// 不考虑错误处理

ASSERT( SUCCEEDED( hr ) );// 如果失败,说明参数传递错误

CString str;// 显示一下结果

str.Format("1 + 2 = %d", vResult.lVal );

AfxMessageBox( str );

pDisp-Release();// 释放接口指针

::CoUninitialize();// 释放 COM

}示例二、CComDispatchDriver 智能指针包装类的使用方法void demo()

{

// 已经进行过了 COM 初始化

CLSID clsid;// 通过 ProgID 取得组件的 CLSID

HRESULT hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &clsid );

ASSERT( SUCCEEDED( hr ) );// 如果失败,说明没有注册组件

CComPtr spUnk;// 由 CLSID 启动组件,并取得 IUnknown 指针

hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IUnknown, (LPVOID *)&spUnk );

ASSERT( SUCCEEDED( hr ) );

CComDispatchDriver spDisp( spUnk );// 构造只能指针

CComVariant v1(1), v2(2), vResult;// 参数

hr = spDisp.Invoke2(// 调用2个参数的函数

L"Add",// 函数名是 Add

&v1,// 第一个参数,值为整数1

&v2,// 第二个参数,值为整数2

&vResult);// 返回值

ASSERT( SUCCEEDED( hr ) );// 如果失败,说明或者没有 ADD 函数,或者参数错误

CString str;// 显示一下结果

str.Format("1 + 2 = %d", vResult.lVal );

AfxMessageBox( str );

}示例程序中使用了 Invoke2()函数,其实你根据不同的函数,还可以使用

Invoke0()、Invoke1()、InvokeN()、PutProperty()、GetProperty()......等等等,的确很方便。

示例三、加载类型库,产生包装类来使用

这个方法使用更简单一些,如果你观察 MFC 帮你产生的包装类的实现,你就会发现,其实它调用的是

IDispatch 接口函数。使用 vc6.0 的朋友,步骤如下:

1、建立一个 MFC 的应用程序

2、开启 ClassWizard,执行 Add Class,选择

From a type library

图二、加载类型库

3、然后找到你要使用的组件文件 simple6.dll(tlb 文件也可以),选择接口后确认

图三、选择类型库中需要包装的接口

4、在适当的地方输入调用代码#include "simple6.h"// 包装类的头文件

void demo()

{

// 已经进行过了 COM 初始化

IDispSimple spDisp;// 包装类的对象

spDisp.CreateDispatch( _T("Simple6.DispSimple.1") )//启动组件

spDisp.xxx(...);// 调用函数

spDisp.ReleaseDispatch();// 释放接口

} 使用 vc.net 的朋友,步骤如下:

1、建立一个 MFC 的应用程序

2、执行菜单“添加\添加类”,选择 MFC 分类中的“类型库中的MFC类”

图四、添加类型库中的MFC类

3、选择组件文件 simple8.dll(或 tlb

文件),并选择需要包装的接口

图五、选择文件和接口

4、在适当的位置输入调用代码#include "CDispSimple.h"// 包装类的头文件

void demo()

{

// 已经进行过了 COM 初始化

CDispSimple spDisp;// 包装类的对象

spDisp.CreateDispatch( _T("Simple8.DispSimple.1") )// 启动组件

spDisp.xxx(...);// 调用函数

spDisp.ReleaseDispatch();// 释放接口

}示例四、使用 #import 方式调用组件

#import 方式在第七回中已经作过介绍,这里就不多罗嗦了。大家下载本回的示例程序后,自己去看吧。并且一定要掌握这个方法,因为它的运行效率是最快的呀。

四、小结

留作业啦。在我们以前所实现的所有组件程序中,只添加了接口方法(函数),而没有添加接口属性(变量),你自己练习一下吧,很简单的,然后写个程序调用看看。其实对于

VC 来说,调用属性和调用方法没有太大的区别(vc 把属性包装为

GetXXX()/PutXXX()或getXXX()/putXXX()的函数方式),但在另外一些语言中(比如脚本语言)则更方便,设置属性值是:对象.属性

= 变量或常量,获取属性值是:变量 = 对象.属性。

本回书至此做一了断,更多组件设计和使用的知识,且听下回分解......

注1:多个自动化接口的实现方法,我们以后再说。

注2:将来介绍 ITypeLib::GetTypeInfo() 的时候,大家再回味

IDispatch::GetTypeInfo()吧。

注3:在后面介绍“事件”的时候,我们会自己真正去实现一个 IDispatch::Invoke()

函数。

注4:介绍多个双接口实现的时候,会谈到这个问题。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有