IDispatch个人理解

 

一个COM组件实现了IDispatch接口就成为自动化组件。

1.       为什么要弄一个IDispatch接口?

如果是编译型语言,那么我们可以让编译器在编译的时候装载类型库(tlb),也就是装载接口的描述。我们分别使用了 #include 方法和 #import 方法来实现的。装载了类型库后,编译器就知道应该如何编译接口函数的调用了---这叫“前绑定”。但是,如果想在脚本语言中使用组件,问题就大了,因为脚本语言是解释执行的,无法装载类型库进行预编译,它执行的时候不会知道具体的函数地址,怎么办?自动化接口就为此诞生了---“后绑定”。自动化组件,其实就是实现了 IDispatch 接口的组件。所以,当需要脚本或解释性语言调用COM组件时才去实现IDispatch接口。

2.       通过IDispatch接口的执行效率如何?

通过IDispatch接口去调用函数,要先通过GetIDsOfNames获取函数ID号,然后Invoke通过这个ID号去调用函数,是一种间接的调用方法,效率相对与通过“前绑定”的方法来的低。正因为这样,ATL产生了一种双接口的模式,在这种模式下,编译型语言会通过前绑定的方法来调用函数,而解释性语言中通过自动化接口来调用,这样兼顾了两边。

3.       使用IDispatch接口的缺点?

1). 为了在各种语言中通讯,必须要使用Variant类型。

2). 因为是间接调用,效率相对比较低。

 

4.  使用双重接口

在实现调度接口时,更好的方法是双重接口,双重接口使得C++程序员能够通过接口的虚函数表访问到,而在宏语言和解释性语言则通过自动化接口去调用。

所谓双接口,其实是在一个 VTAB 的虚函数表中容纳了三个接口(因为任何接口都是从 IUnknown 派生的,所以就不强调 IUnknown 了,叫做双接口)。我们如果从任意一个接口中调用 QueryInterface()得到另外的接口指针的话,其实,得到的指针地址都是同一个。

从图中可以看出,Add(),Upper()……这两个函数既可以通过接口IXXX通过Vtabl表直接调用,这样效率高,用于编译型语言,同时又可通过获取调度ID用Invoke去调用,效率相对低,一般在解释性语言中使用。当然也可在编译型语言中通过Invoke去调用,但就相当麻烦,如下:(不过,通过这种方法,可以不加载类型库或头文件)

::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;    // 第二个参数,整数2v[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

 

双接口的缺点:

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

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

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

所以,如果不支持脚本,不要用双接口

补充:关于双接口缺点的第一点,个人认为有办法弥补,就是在双接口参数中使用类型LPDISPATCH  参考文章http://www.vckbase.com/document/viewdoc/?id=1159关于 IDispatch 接口的 LPDispatch 属性的实现

 

4.       如何使用自动化组件?

具体例子参考11 

示例程序

自动化组件的使用方式

简要说明

示例0

在脚本中调用

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

示例1

使用 API 方式调用

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

示例2

使用 CComDispatchDriver 的智能指针包装类

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

示例3

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

简单!好用!常用!但它本质上是使用 IDispatch 接口,所以执行效率稍差

示例4

使用 #import 方式加载类型库方式

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

 

    刚搞COM,道行不深,有错误请指出!thankyou

(以上很多摘自VCKBASE上杨老师的文章中,若杨老师看见了,敬请原谅!)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值