• 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
     
     在CODE上查看代码片派生到我的代码片
    1. IDispatch : public IUnknown  
    2. {  
    3. public:  
    4.     virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(   
    5.         /* [out] */ __RPC__out UINT *pctinfo) = 0;  
    6.       
    7.     virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(   
    8.         /* [in] */ UINT iTInfo,  
    9.         /* [in] */ LCID lcid,  
    10.         /* [out] */ __RPC__deref_out_opt ITypeInfo **ppTInfo) = 0;  
    11.       
    12.     virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(   
    13.         /* [in] */ __RPC__in REFIID riid,  
    14.         /* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR *rgszNames,  
    15.         /* [range][in] */ __RPC__in_range(0,16384) UINT cNames,  
    16.         /* [in] */ LCID lcid,  
    17.         /* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID *rgDispId) = 0;  
    18.       
    19.     virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke(   
    20.         /* [annotation][in] */   
    21.         _In_  DISPID dispIdMember,  
    22.         /* [annotation][in] */   
    23.         _In_  REFIID riid,  
    24.         /* [annotation][in] */   
    25.         _In_  LCID lcid,  
    26.         /* [annotation][in] */   
    27.         _In_  WORD wFlags,  
    28.         /* [annotation][out][in] */   
    29.         _In_  DISPPARAMS *pDispParams,  
    30.         /* [annotation][out] */   
    31.         _Out_opt_  VARIANT *pVarResult,  
    32.         /* [annotation][out] */   
    33.         _Out_opt_  EXCEPINFO *pExcepInfo,  
    34.         /* [annotation][out] */   
    35.         _Out_opt_  UINT *puArgErr) = 0;  
    36.       
    37. };  

    GetTypeInfoCount和GetTypeInfo以后再说。

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

    GetIDsOfNames

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

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. typedef LONG DISPID;  

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

    [html] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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
     
     在CODE上查看代码片派生到我的代码片
    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.   
    7.     return S_OK;  
    8. }  

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

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. CComPtr<IMyCar> spCar;  
    2. spCar.CoCreateInstance(CLSID_MyCar);  
    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. DISPID PropertyID[3] = {0};  
    2. BSTR PropName[3];  
    3.       
    4. PropName[0] = SysAllocString(L"AddGas");  
    5. PropName[1] = SysAllocString(L"add");  
    6. PropName[2] = SysAllocString(L"total");  
    7. HRESULT hr = spCar->GetIDsOfNames(IID_NULL, PropName, 3, LOCALE_SYSTEM_DEFAULT, PropertyID);  
    8.   
    9. SysFreeString(PropName[0]);  
    10. SysFreeString(PropName[1]);  
    11. SysFreeString(PropName[2]);  

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

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

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

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

    Invoke

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

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    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
     
     在CODE上查看代码片派生到我的代码片
    1. CComVariant avarParams[2];  
    2. avarParams[1].vt = VT_I4;  
    3. avarParams[1] = 4;  
    4.   
    5. LONG vTotal = 0;  
    6. avarParams[0].vt = VT_I4 | VT_BYREF;  
    7. avarParams[0] = &vTotal;  
    8.   
    9. DISPPARAMS params = { avarParams,  
    10.     NULL,              // Dispatch identifiers of named arguments.   
    11.     2,                 // Number of arguments.  
    12.     0 };                // Number of named arguments.  
    13.   
    14. 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
     
     在CODE上查看代码片派生到我的代码片
    1. DISPID PropertyID2[1] = { 0 };  
    2. BSTR PropName2[1];  
    3.   
    4. PropName2[0] = SysAllocString(L"Gas");  
    5.   
    6. hr = spCar->GetIDsOfNames(IID_NULL, PropName2, 1, LOCALE_SYSTEM_DEFAULT, PropertyID2);  
    7.   
    8. SysFreeString(PropName2[0]);  
    9.   
    10.   
    11. DISPPARAMS params2 = { NULL,  
    12.     NULL,  
    13.     0,  
    14.     0  
    15. };  
    16.   
    17. CComVariant Result;  
    18. 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
     
     在CODE上查看代码片派生到我的代码片
    1. // ConsoleApplication4.cpp : Defines the entry point for the console application.  
    2. //  
    3.   
    4. #include "stdafx.h"  
    5.   
    6. #include <thread>  
    7. #include <atlbase.h>  
    8. #include <atlcom.h>  
    9. #include <algorithm>  
    10. #include <vector>  
    11. #include <memory>  
    12.   
    13. #include "../MyCom/MyCom_i.h"  
    14. #include "../MyCom/MyCom_i.c"  
    15.   
    16. int _tmain(int argc, _TCHAR* argv[])  
    17. {  
    18.     CoInitializeEx(NULL, COINIT_MULTITHREADED);  
    19.       
    20.     CComPtr<IMyCar> spCar;  
    21.     spCar.CoCreateInstance(CLSID_MyCar);  
    22.   
    23.     // use IDispatch  
    24.     DISPID PropertyID[3] = {0};  
    25.     BSTR PropName[3];  
    26.           
    27.     PropName[0] = SysAllocString(L"AddGas");  
    28.     PropName[1] = SysAllocString(L"add");  
    29.     PropName[2] = SysAllocString(L"total");  
    30.     HRESULT hr = spCar->GetIDsOfNames(IID_NULL, PropName, 3, LOCALE_SYSTEM_DEFAULT, PropertyID);  
    31.   
    32.     SysFreeString(PropName[0]);  
    33.     SysFreeString(PropName[1]);  
    34.     SysFreeString(PropName[2]);  
    35.   
    36.     CComVariant avarParams[2];  
    37.     avarParams[1].vt = VT_I4;  
    38.     avarParams[1] = 4;  
    39.   
    40.     LONG vTotal = 0;  
    41.     avarParams[0].vt = VT_I4 | VT_BYREF;  
    42.     avarParams[0] = &vTotal;  
    43.   
    44.     DISPPARAMS params = { avarParams,  
    45.         NULL,              // Dispatch identifiers of named arguments.   
    46.         2,                 // Number of arguments.  
    47.         0 };                // Number of named arguments.  
    48.   
    49.     hr = spCar->Invoke(PropertyID[0], IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, NULL, NULL, NULL);  
    50.       
    51.   
    52.     DISPID PropertyID2[1] = { 0 };  
    53.     BSTR PropName2[1];  
    54.   
    55.     PropName2[0] = SysAllocString(L"Gas");  
    56.       
    57.     hr = spCar->GetIDsOfNames(IID_NULL, PropName2, 1, LOCALE_SYSTEM_DEFAULT, PropertyID2);  
    58.   
    59.     SysFreeString(PropName2[0]);  
    60.   
    61.   
    62.     DISPPARAMS params2 = { NULL,  
    63.         NULL,  
    64.         0,  
    65.         0  
    66.     };  
    67.       
    68.     CComVariant Result;  
    69.     hr = spCar->Invoke(PropertyID2[0], IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, ¶ms2, &Result, NULL, NULL);  
    70.       
    71.     spCar.Release();  
    72.   
    73.     CoUninitialize();  
    74.       
    75.     return 0;  
    76. }  

    相关的COM组件的代码:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
      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.   
      7.     return S_OK;  
      8. }  
      9.   
      10.   
      11. STDMETHODIMP CMyCar::get_Gas(LONG* pVal)  
      12. {  
      13.     // TODO: Add your implementation code here  
      14.     *pVal = m_Gas;  
      15.   
      16.     return S_OK;  
      17. }  
  • 相关阅读:
    python修改python unittest的运行顺序
    史上最强大的python selenium webdriver的包装
    第六种方式,python使用cached_property缓存装饰器和自定义cached_class_property装饰器,动态添加类属性(三),selnium webdriver类无限实例化控制成单浏览器。
    python带参装饰器的改良版
    第五种方式,python使用组合来添加类方法和属性(二),以selenium的webdriver为例
    python装饰器、继承、元类、mixin,四种給类动态添加类属性和方法的方式(一)
    linux添加PYTHONPATH环境变量
    linux 按照端口一句命令杀死进程,按照进程名称一句命令杀死进程
    python __all__用法
    使用pycharm,追求最优的代码。
  • 原文地址:https://www.cnblogs.com/jzxx/p/5521026.html
Copyright © 2020-2023  润新知