IDispatch接口介绍

1.         C程序调用时,调用者必须预先知道接口规范(如,参数类型、参数字节长度、参数顺序等)。由于不同语言这些规范有所不同,COM未解决不同语言之间调用,提供了IDispatch接口。

2.         IDispatch要求其实例必须自我描述,即拿到一个对象后,可从对象中直接获取调用方式,而无须预先明确。

3.         IDispatch中通过VT_TYPE来指定相关类型,如 VT_I4为4字节整形、VT_BSTR为unicode字符串,VT_DISPATCH表示是一个IDispatch对象

4.         给对象中每一属性或函数(Method)分配一个整形Id和一个字符串name,调用者可以通过name字符串确定如何调用。如,若name为"length"的属性,调用者就理解为长度。由于这里通常是根据name来理解相应属性,因此name描述应足够准确。如,以"length()"为名称的函数实现整数相加功能就是不恰当的。

5.         使用IDispatch对象时,首相调用 IDispatch::GetIDsOfNames()将属性、函数名称作为参数,获取对应的属性、函数id。

6.         再调用IDispatch::Invoke() 将id作为参数,实际调用功能。

7.         若为获取属性值,则 Invoke()调用时,传入 Dispatch_PropertyGet标志。

8.         若为设置属性值,则Invoke()调用时,传入 Dispatch_PropertyPut标志。并在 DispParams参数中指定修该属性改为何值。DispParams结构说明见后。

9.         若为调用函数,则 Invoke()调用时,传入 Dispatch_Method标志。若该Method需要参数,则通过IDispatch::Invoke()的DispParams参数指定。

10.     DispParams结构使用举例:

DISPPARAMS dispparams;

dispparams.rgdispidNamedArgs = &dispidOfNamedArgs;

dispparams.cArgs = 1;

dispparams.cNamedArgs = 1;

dispparams.rgvarg = new VARIANTARG[1];

dispparams.rgvarg[0].vt = VT_I4;

dispparams.rgvarg[0].intVal = 123;

a.         上面代码是一个用于给Method传参的简单例子,先创建一个DispParams对象

b.         cArgs指定Method中的参数个数。

c.         cNamedArgs指定Method中已经命名的参数个数。(命名参数是对应无名参数的概念。有些语言可定义不定参数,此时IDispatch的描述中不会给参数分配名称,而是调用时以无名参数存在。如,JS中 Array对象的push()方法,可支持不定个数的参数)

d.         rgvarg 为实际参数数组,每一元素表示一个参数,其中.vt表明此元素的数据类型,intVal项是一个C++联合结构,如vt == VT_I4时,应以intVal = xxx方式赋值;若 vt == VT_BSTR,则应以 bstrVal = xxx方式赋值

11.     举例:两个参数,都是无名称参数,第一个为整形,第二个为BSTR型

DISPPARAMS dispparams;

dispparams.rgdispidNamedArgs = NULL;

dispparams.cArgs = 2;

dispparams.cNamedArgs = 0;

dispparams.rgvarg = new VARIANTARG[2]; // 2个参数,分配2个空间

dispparams.rgvarg[0].vt = VT_I4;  // 整形

dispparams.rgvarg[0].intVal = 123;

dispparams.rgvarg[1].vt = VT_BSTR; // 字符串型

dispparams.rgvarg[1].bstrVal = L"abcd";

IDispatch接口是COM自动化的核心。其实,IDispatch这个接口本身也很简单,只有4个方法:

[cpp] view plain copy

  1. IDispatch : public IUnknown
  2. {
  3. public:
  4. virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(
  5. /* [out] */ __RPC__out UINT *pctinfo) = 0;
  6. virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(
  7. /* [in] */ UINT iTInfo,
  8. /* [in] */ LCID lcid,
  9. /* [out] */ __RPC__deref_out_opt ITypeInfo **ppTInfo) = 0;
  10. virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(
  11. /* [in] */ __RPC__in REFIID riid,
  12. /* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR *rgszNames,
  13. /* [range][in] */ __RPC__in_range(0,16384) UINT cNames,
  14. /* [in] */ LCID lcid,
  15. /* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID *rgDispId) = 0;
  16. virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke(
  17. /* [annotation][in] */
  18. _In_  DISPID dispIdMember,
  19. /* [annotation][in] */
  20. _In_  REFIID riid,
  21. /* [annotation][in] */
  22. _In_  LCID lcid,
  23. /* [annotation][in] */
  24. _In_  WORD wFlags,
  25. /* [annotation][out][in] */
  26. _In_  DISPPARAMS *pDispParams,
  27. /* [annotation][out] */
  28. _Out_opt_  VARIANT *pVarResult,
  29. /* [annotation][out] */
  30. _Out_opt_  EXCEPINFO *pExcepInfo,
  31. /* [annotation][out] */
  32. _Out_opt_  UINT *puArgErr) = 0;
  33. };

GetTypeInfoCount和GetTypeInfo以后再说。

先来看看比较熟悉的GetIDsOfNames和Invoke。

GetIDsOfNames

这个函数的主要功能就是:把COM接口的方法名字和参数(可选)映射成一组DISPID。DISPID就是一个LONG型:

[cpp] view plain copy

  1. typedef LONG DISPID;

GetIDsOfNames()可以获取方法和属性。先来看一个例子,COM接口IMyCar

[html] view plain copy

  1. [
  2. object,
  3. uuid(21B794E2-4857-4576-8FC2-CDAB2A486600),
  4. dual,
  5. nonextensible,
  6. pointer_default(unique)
  7. ]
  8. interface IMyCar : IDispatch{
  9. [id(1)] HRESULT Run();
  10. [id(2)] HRESULT AddGas([in] LONG add, [out] LONG* total);
  11. [propget, id(3)] HRESULT Gas([out, retval] LONG* pVal);
  12. };

这个接口里面有一个方法AddGas,它有两个参数,一个输入,一个输出。它的实现基本如下:

[cpp] view plain copy

  1. STDMETHODIMP CMyCar::AddGas(LONG add, LONG* total)
  2. {
  3. // TODO: Add your implementation code here
  4. m_Gas += add;
  5. *total = m_Gas;
  6. return S_OK;
  7. }

试试如何获取AddGas函数的id和参数index,看下面的代码。数组里面的第一个元素是方法名字,第二个/第三个是参数名字。

[cpp] view plain copy

  1. CComPtr<IMyCar> spCar;
  2. spCar.CoCreateInstance(CLSID_MyCar);

[cpp] view plain copy

  1. DISPID PropertyID[3] = {0};
  2. BSTR PropName[3];
  3. PropName[0] = SysAllocString(L"AddGas");
  4. PropName[1] = SysAllocString(L"add");
  5. PropName[2] = SysAllocString(L"total");
  6. HRESULT hr = spCar->GetIDsOfNames(IID_NULL, PropName, 3, LOCALE_SYSTEM_DEFAULT, PropertyID);
  7. SysFreeString(PropName[0]);
  8. SysFreeString(PropName[1]);
  9. SysFreeString(PropName[2]);

运行一下,可以得到如下结果:

PropertyID数组里面可以得到3个值:2, 0, 1.

2代表的是AddGas的id,跟idl文件里面的一样。

0表示"add"是第一个参数,1表示"total"是第二个参数。

Invoke

Invoke是IDispatch里面非常重要的一样函数,方法调用就靠这个函数了。函数原型:

[cpp] view plain copy

  1. HRESULT Invoke(
  2. [in]       DISPID dispIdMember,
  3. [in]       REFIID riid,
  4. [in]       LCID lcid,
  5. [in]       WORD wFlags,
  6. [in, out]  DISPPARAMS *pDispParams,
  7. [out]      VARIANT *pVarResult,
  8. [out]      EXCEPINFO *pExcepInfo,
  9. [out]      UINT *puArgErr
  10. );

每一个参数的说明,看下面,从MSDN截来的。

先来看一个调用例子:

[cpp] view plain copy

  1. CComVariant avarParams[2];
  2. avarParams[1].vt = VT_I4;
  3. avarParams[1] = 4;
  4. LONG vTotal = 0;
  5. avarParams[0].vt = VT_I4 | VT_BYREF;
  6. avarParams[0] = &vTotal;
  7. DISPPARAMS params = { avarParams,
  8. NULL,              // Dispatch identifiers of named arguments.
  9. 2,                 // Number of arguments.
  10. 0 };                // Number of named arguments.
  11. hr = spCar->Invoke(PropertyID[0], IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &params, NULL, NULL, NULL);

avarParams是一个具有2个元素的数组,它表示要调用的方法的参数,注意这里的参数是逆序的。比如avarParam[1]存放的是第一个参数,是一个输入参数;avarParams[0]存放的是第二个参数,是一个输出参数。

运行一下:

spCar里面的m_Gas初始值是0,我们调用的时候给它加了4,那么输出就应该是4.看上面的运行结果,vTotal确实是4.

这样我们就通过Invoke成功调用了COM组件的方法(而不是通过spCar->AddGas)。注意Invoke里面的第一个参数是一个DISPID,这个是从GetIDsOfNames来的。

使用Invoke也可以调用COM对象的属性。

比如上面的idl里面的Gas。

调用属性跟方法差不多,如果我们想读取一个属性,那么可以这么调:

[cpp] view plain copy

  1. DISPID PropertyID2[1] = { 0 };
  2. BSTR PropName2[1];
  3. PropName2[0] = SysAllocString(L"Gas");
  4. hr = spCar->GetIDsOfNames(IID_NULL, PropName2, 1, LOCALE_SYSTEM_DEFAULT, PropertyID2);
  5. SysFreeString(PropName2[0]);
  6. DISPPARAMS params2 = { NULL,
  7. NULL,
  8. 0,
  9. 0
  10. };
  11. CComVariant Result;
  12. hr = spCar->Invoke(PropertyID2[0], IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, &params2, &Result, NULL, NULL);

运行可以得到结果:

注意,Invoke的第五个参数啥都没,属性值是从第六个参数返回出来的。如果是方法调用的话,那么COM方法参数需要从第五个参数传进入,第六个参数是函数调用的返回值HRESULT.

我没有试过属性的put,不知道是从哪里传入,当有需要的时候查一下MSDN就行了。

以上就是GetIDsOfNames和Invoke的简要说明以及例子。之后再来分析更多的细节。

完整客户端代码:

[cpp] view plain copy

  1. // ConsoleApplication4.cpp : Defines the entry point for the console application.
  2. //
  3. #include "stdafx.h"
  4. #include <thread>
  5. #include <atlbase.h>
  6. #include <atlcom.h>
  7. #include <algorithm>
  8. #include <vector>
  9. #include <memory>
  10. #include "../MyCom/MyCom_i.h"
  11. #include "../MyCom/MyCom_i.c"
  12. int _tmain(int argc, _TCHAR* argv[])
  13. {
  14. CoInitializeEx(NULL, COINIT_MULTITHREADED);
  15. CComPtr<IMyCar> spCar;
  16. spCar.CoCreateInstance(CLSID_MyCar);
  17. // use IDispatch
  18. DISPID PropertyID[3] = {0};
  19. BSTR PropName[3];
  20. PropName[0] = SysAllocString(L"AddGas");
  21. PropName[1] = SysAllocString(L"add");
  22. PropName[2] = SysAllocString(L"total");
  23. HRESULT hr = spCar->GetIDsOfNames(IID_NULL, PropName, 3, LOCALE_SYSTEM_DEFAULT, PropertyID);
  24. SysFreeString(PropName[0]);
  25. SysFreeString(PropName[1]);
  26. SysFreeString(PropName[2]);
  27. CComVariant avarParams[2];
  28. avarParams[1].vt = VT_I4;
  29. avarParams[1] = 4;
  30. LONG vTotal = 0;
  31. avarParams[0].vt = VT_I4 | VT_BYREF;
  32. avarParams[0] = &vTotal;
  33. DISPPARAMS params = { avarParams,
  34. NULL,              // Dispatch identifiers of named arguments.
  35. 2,                 // Number of arguments.
  36. 0 };                // Number of named arguments.
  37. hr = spCar->Invoke(PropertyID[0], IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, NULL, NULL, NULL);
  38. DISPID PropertyID2[1] = { 0 };
  39. BSTR PropName2[1];
  40. PropName2[0] = SysAllocString(L"Gas");
  41. hr = spCar->GetIDsOfNames(IID_NULL, PropName2, 1, LOCALE_SYSTEM_DEFAULT, PropertyID2);
  42. SysFreeString(PropName2[0]);
  43. DISPPARAMS params2 = { NULL,
  44. NULL,
  45. 0,
  46. 0
  47. };
  48. CComVariant Result;
  49. hr = spCar->Invoke(PropertyID2[0], IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms2, &Result, NULL, NULL);
  50. spCar.Release();
  51. CoUninitialize();
  52. return 0;
  53. }

相关的COM组件的代码:

[cpp] view plain copy

    1. STDMETHODIMP CMyCar::AddGas(LONG add, LONG* total)
    2. {
    3. // TODO: Add your implementation code here
    4. m_Gas += add;
    5. *total = m_Gas;
    6. return S_OK;
    7. }
    8. STDMETHODIMP CMyCar::get_Gas(LONG* pVal)
    9. {
    10. // TODO: Add your implementation code here
    11. *pVal = m_Gas;
    12. return S_OK;
    13. }
时间: 2024-10-11 11:57:57

IDispatch接口介绍的相关文章

【转载】COM 组件设计与应用(九)——IDispatch 接口 for VC6.0

原文: http://vckbase.com/index.php/wv/1224.html 一.前言 终于写到了第九回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常用.非常有用.非常精彩的一个 COM 功能.由于 WORD.EXCEL 等 OFFICE 软件提供了“宏”的功能,就连我们使用的VC开发环境也提供了“宏”功能,更由于 HTML.ASP.JSP 等都要依靠脚本(Script)的支持,更体现出了自动化接口的重要性. 如果你使用 vc6.0 的开发环境,

【转载】COM 组件设计与应用(十)——IDispatch 接口 for VC.NET

原文:http://vckbase.com/index.php/wv/1225.html 一.前言 终于写到了第十回,我也一直期盼着写这回的内容耶,为啥呢?因为自动化(automation)是非常常用.非常有用.非常精彩的一个 COM 功能.由于 WORD.EXCEL 等 OFFICE 软件提供了“宏”的功能,就连我们使用的VC开发环境也提供了“宏”功能,更由于 HTML.ASP.JSP 等都要依靠脚本(Script)的支持,更体现出了自动化接口的重要性. 如果你使用 vc6.0 的开发环境,请

SPI、I2C、UART三种串行总线协议的区别和SPI接口介绍(转)

SPI.I2C.UART三种串行总线协议的区别 第一个区别当然是名字: SPI(Serial Peripheral Interface:串行外设接口); I2C(INTER IC BUS) UART(Universal Asynchronous Receiver Transmitter:通用异步收发器) 第二,区别在电气信号线上: SPI总线由三条信号线组成:串行时钟(SCLK).串行数据输出(SDO).串行数据输入(SDI).SPI总线可以实现多个SPI设备互相连接.提供SPI串行时钟的SPI

Hive 接口介绍(Web UI/JDBC)

Hive 接口介绍(Web UI/JDBC) 实验简介 本次实验学习 Hive 的两种接口:Web UI 以及 JDBC. 一.实验环境说明 1. 环境登录 无需密码自动登录,系统用户名shiyanlou,密码shiyanlou 2. 环境介绍 本实验环境采用带桌面的Ubuntu Linux环境,实验中会用到桌面上的程序: XfceTerminal: Linux命令行终端,打开后会进入Bash环境,可以使用Linux命令: Firefox:浏览器,可以用在需要前端界面的课程里,只需要打开环境里写

Spring的BeanPostProcesser接口介绍

前言 废话不多说,直接进入主题. 同学们有想过这么一种情况吗:Spring容器提供给我们的一些接口实现类并不能满足我们的要求,但是我们又不想重新写一个类,只想在原来类上修改一些属性? 举个例子,SpringMVC中通过<mvc:annotation-driven>标签自动生成的RequestMappingHandlerAdapter有个HandlerMethodArgumentResolverComposite类型的argumentResolvers属性,这个属性内部有个HandlerMeth

核心开发接口介绍

核心开发接口介绍1.hibernate_0500_CoreAPI2.HiberanteAPI 文档需要单独下载 在线api链接:http://docs.jboss.org/hibernate/core/3.3/api3.Configuration a) AnnotationConfiguration b) 进行配置信息的管理 c) 用来产生 SessionFactory d) 可以在 configure方法中指定hibernate配置文件 e) 只需关注一个方法即:buildSessionFac

微信公众平台开发(一)---接口介绍及配置

一.公众平台开发接口介绍 公众平台是为微信用户提供服务的平台,而公众平台开发接口则是提供服务的基础,开发者在公众平台网站中创建公众号.获取接口权限后,可以通过阅读本接口文档来帮助开发. 公众平台开发接口提供与用户进行消息交互.自定义菜单交互的能力.对于成功接入公众平台开发接口的公众账号,当用户发消息给公众号,微信公众平台服务器会使用http请求对接入的网址进行消息推送,第三方服务器可通过响应包回复特定结构,从而达到回复消息的目的. 二.注册微信平台公众帐号  (1)注册地址:https://mp

IDispatch接口 - GetIDsOfNames和Invoke

IDispatch接口是COM自动化的核心.其实,IDispatch这个接口本身也很简单,只有4个方法: IDispatch : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount( /* [out] */ __RPC__out UINT *pctinfo) = 0; virtual HRESULT STDMETHODCALLTYPE GetTypeInfo( /* [in] */ UINT i

群发软件/推广软件接口介绍

一.接口介绍: 1. /** * 代表本系统需要开发的网站的基本操作,比如注册.登录.发布等这些一般需要开发的操作. * 除了本接口还有有一些辅助接口,比如{@link InteractAble},他们的共同目的都是为了 完善网站需要的操作. * <p> * 实现该接口,系统将会在你在表现层上触发相应事件的时候调用本接口的相应方法, 比如你点击了<i>注册</i> * 按钮后,将会触发{@code register()}方法. * * @author ocq * @si