zoukankan      html  css  js  c++  java
  • C++ COM编程-IDispatch介绍

    1. 基本详情
    IDispatch是由OLE自动化协议暴露出来的接口。
    IDispatch可以由IUnknown得到,并且在IUnknown本身所含有三个方法(AddRef,Release和QueryInterface)上增加另外四个方法(GetTypeInfoCount,GetTypeInfo,GetIDsOfNames和Invoke)。

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

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

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

    4. 使用IDispatch接口的缺点
    1). 为了在各种语言中通讯,必须要使用Variant类型。

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

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

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

    函数既可以通过接口IXXX通过Vtabl表直接调用,这样效率高,用于编译型语言,同时又可通过获取调度ID用Invoke去调用,效率相对低,一般在解释性语言中使用。

    当然也可在编译型语言中通过Invoke去调用,但就相当麻烦,如下:(不过,通过这种方法,可以不加载类型库或头文件)

     1 ::CoInitialize( NULL );               // COM 初始化
     2  
     3 CLSID clsid;                   // 通过 ProgID 得到 CLSID
     4 HRESULT hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &clsid );
     5 ASSERT( SUCCEEDED( hr ) );     // 如果失败,说明没有注册组件
     6 IDispatch * pDisp = NULL;  
     7 // 由 CLSID 启动组件,并得到 IDispatch 指针
     8 hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IDispatch, (LPVOID *)&pDisp );
     9 ASSERT( SUCCEEDED( hr ) );     // 如果失败,说明没有初始化 COM
    10 LPOLESTR pwFunName = L"Add";   // 准备取得 Add 函数的序号 DispID
    11 DISPID dispID;                // 取得的序号,准备保存到这里
    12 hr = pDisp->GetIDsOfNames(            // 根据函数名,取得序号的函数
    13                IID_NULL,
    14                &pwFunName,          // 函数名称的数组
    15                1,                   // 函数名称数组中的元素个数
    16                LOCALE_SYSTEM_DEFAULT,     // 使用系统默认的语言环境
    17                &dispID );          // 返回值
    18 ASSERT( SUCCEEDED( hr ) );        // 如果失败,说明组件根本就没有 ADD 函数
    19 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
    20 DISPPARAMS dispParams = { v, NULL, 2, 0 };    // 把参数包装在这个结构中
    21         VARIANT vResult;                      // 函数返回的计算结果
    22 hr = pDisp->Invoke(                   // 调用函数
    23                dispID,               // 函数由 dispID 指定
    24                IID_NULL,
    25                LOCALE_SYSTEM_DEFAULT, // 使用系统默认的语言环境
    26                DISPATCH_METHOD,           // 调用的是方法,不是属性
    27                &dispParams,                // 参数
    28                &vResult,                          // 返回值
    29                NULL,                       // 不考虑异常处理
    30                NULL);                      // 不考虑错误处理
    31 ASSERT( SUCCEEDED( hr ) );     // 如果失败,说明参数传递错误
    32 CString str;                   // 显示一下结果
    33 str.Format("1 + 2 = %d", vResult.lVal );
    34 AfxMessageBox( str );
    35  
    36 pDisp->Release();              // 释放接口指针
    37 ::CoUninitialize();            // 释放 COM

    6. IDispatch接口代码参考

    IDispatch及双接口的调用
    ① 使用API方式调用:

     1 ::CoInitialize( NULL );        // COM 初始化
     2 
     3 CLSID clsid;                // 通过 ProgID 得到 CLSID
     4 HRESULT hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &clsid );
     5 ASSERT( SUCCEEDED( hr ) );    // 如果失败,说明没有注册组件
     6 
     7 IDispatch * pDisp = NULL;    // 由 CLSID 启动组件,并得到 IDispatch 指针
     8 hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IDispatch, (LPVOID *)&pDisp );
     9 ASSERT( SUCCEEDED( hr ) );    // 如果失败,说明没有初始化 COM
    10 
    11 LPOLESTR pwFunName = L"Add";    // 准备取得 Add 函数的序号 DispID
    12 DISPID dispID;                    // 取得的序号,准备保存到这里
    13 hr = pDisp->GetIDsOfNames(        // 根据函数名,取得序号的函数
    14          IID_NULL,
    15          &pwFunName,                    // 函数名称的数组
    16          1,                            // 函数名称数组中的元素个数
    17          LOCALE_SYSTEM_DEFAULT,        // 使用系统默认的语言环境
    18          &dispID );                    // 返回值
    19 ASSERT( SUCCEEDED( hr ) );        // 如果失败,说明组件根本就没有 ADD 函数
    20 
    21 VARIANTARG v[2];                    // 调用 Add(1,2) 函数所需要的参数
    22 v[0].vt = VT_I4;    v[0].lVal = 2;    // 第二个参数,整数2
    23 v[1].vt = VT_I4;    v[1].lVal = 1;    // 第一个参数,整数1
    24 
    25 DISPPARAMS dispParams = { v, NULL, 2, 0 };    // 把参数包装在这个结构中
    26 VARIANT vResult;            // 函数返回的计算结果
    27 
    28 hr = pDisp->Invoke(            // 调用函数9         dispID,                    // 函数由 dispID 指定
    29          IID_NULL,
    30          LOCALE_SYSTEM_DEFAULT,    // 使用系统默认的语言环境
    31          DISPATCH_METHOD,        // 调用的是方法,不是属性
    32          &dispParams,            // 参数
    33          &vResult,                // 返回值
    34          NULL,                    // 不考虑异常处理
    35          NULL);                    // 不考虑错误处理
    36 ASSERT( SUCCEEDED( hr ) );    // 如果失败,说明参数传递错误
    37 
    38 CString str;            // 显示一下结果
    39 str.Format("1 + 2 = %d", vResult.lVal );
    40 AfxMessageBox( str );
    41 
    42 pDisp->Release();        // 释放接口指针
    43 ::CoUninitialize();        // 释放 COM

    ② 使用智能指针包装类:

     1 // 在 App 类,InitInstance 中,已经调用 AfxOleInit() 进行了 COM 初始化
     2 
     3 CLSID clsid;                // 通过 ProgID 取得组件的 CLSID
     4 HRESULT hr = ::CLSIDFromProgID( L"Simple8.DispSimple.1", &clsid );
     5 ASSERT( SUCCEEDED( hr ) );    // 如果失败,说明没有注册组件
     6 
     7 CComPtr < IUnknown > spUnk;    // 由 CLSID 启动组件,并取得 IUnknown 指针
     8 hr = ::CoCreateInstance( clsid, NULL, CLSCTX_ALL, IID_IUnknown, (LPVOID *)&spUnk );
     9 ASSERT( SUCCEEDED( hr ) );
    10 
    11 CComDispatchDriver spDisp( spUnk );    // 构造只能指针
    12 CComVariant v1(1), v2(2), vResult;    // 参数
    13 hr = spDisp.Invoke2(    // 调用2个参数的函数
    14          L"Add",                // 函数名是 Add
    15          &v1,                // 第一个参数,值为整数1
    16          &v2,                // 第二个参数,值为整数2
    17          &vResult);            // 返回值
    18 ASSERT( SUCCEEDED( hr ) );    // 如果失败,说明或者没有 ADD 函数,或者参数错误
    19 CString str;            // 显示一下结果
    20 str.Format("1 + 2 = %d", vResult.lVal );
    21 AfxMessageBox( str );
    22 
    23 // spUnk 和 spDisp 都是智能指针,会自动释放
    24 // 如果使用 CoInitialize(NULL) 初始化,则必须在 CoUninitialize() 之前
    25 // 调用 spUnk.Release() 和 spDisp.Release() 释放
  • 相关阅读:
    批量拷贝局域网内的文件
    ★★★感谢伤害你的人★★★
    asp 调用子程序不能使用括号 错误解决办法
    喜欢在网上写日志的人是不是都想有朝一日被人看到?
    星语心愿
    执着
    推荐一个.NET(C#)的HTTP辅助类组件restsharp
    如何让DEV跳出的“提示试用版”的对话框不再显示
    Debugging with GDB (3) 退出gdb
    Debugging with GDB (1) 介绍
  • 原文地址:https://www.cnblogs.com/ybqjymy/p/15146344.html
Copyright © 2011-2022 走看看