ATL实现一个组件多个dual接口,multidisp

最近想自己写个按键精灵的插件,于是接触到这个问题: 怎么在一个组件里实现两个自动化接口。

主要针对的ATL,MFC貌似没这个问题,具体MFC是怎么实现的自己没有深究。

按键精灵的插件会在一个组件里实现两个dispinterface,具体请看oleview工具截图:

刚开始对这个问题不理解,以为不是问题,自己用ATL尝试了几次,才发现不是那么回事,于是google之。

MSDN上是这么说的,看这里

ATL不提供任何为将多个双重接口支持。IDispatch的单个实现。 但是,有几个已知的方法来手动合并接口,如创建包含创建一个新的对象,执行 QueryInterface 函数或使用嵌套的对象一个基于typeinfo的实现的单独 IDispatch 接口来创建 IDispatch 接口的模板选件类。

这些方法都有潜在的命名空间冲突问题,以及代码复杂性和可维护性。 建议不要创建多个双绑定接口。

虽然ATL不支持,但是上面也说了,还是有方法的,于是再google之,终于找到一篇相关问题的文章,里面说的很细,还提供了几种不同的方案:

网址:https://www.sellsbrothers.com/posts/details/12657

自己比较喜欢第2和第3种方案,对比来说,第3种方案比较容易理解和实现。

当然我是用的第3种方案的简单实现,没有从typeinfo接口再继承,还是自己实现了一个类,代理其实接口的IDispatch调用,废话不说了,上代码:

#ifndef _XMULTIDISPIMPL_H_
#define _XMULTIDISPIMPL_H_
#include <atlcom.h>

#define INTERFACE_MASK 0xFFFF0000UL
#define DISPID_MASK    0x0000FFFFUL

template<class tihclass = CComTypeInfoHolder>
struct _TIH_ENTRY {
    tihclass *ptih;            // 类型库指针,实现IDispatch调用
    DWORD dispEncode;          // 函数调用id编码,在GetIdsOfNames函数中对返回的dispid进行编码,尝试解决dispid重复的问题
    DWORD offset;              // 接口虚函数表偏移,IDispatchImpl<...>
};

template <class T, class tihclass = CComTypeInfoHolder>
class ATL_NO_VTABLE XMultiDispImpl: public IDispatch
{
public:
    typedef _TIH_ENTRY<tihclass> TIH_ENTRY;

public:
    STDMETHOD(GetTypeInfoCount)(UINT* pctinfo)
    {
        //TODO: 考虑是否按多个类型库处理
        *pctinfo = 1;
        return S_OK;
    }
    STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo** pptinfo)
    {
        //TODO: 考虑是否按多个类型库处理
        T* pT = static_cast<T*> (this);
        TIH_ENTRY* pEntry = pT->GetTypeInfoHolder();
        if (pEntry->ptih)
        {
            // 默认返回第一个接口的类型库
            return pEntry->ptih->GetTypeInfo(itinfo, lcid, pptinfo);
        }
        return E_FAIL;
    }
    STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR* rgszNames,
        UINT cNames, LCID lcid, DISPID* rgdispid)
    {
        // NOTE: 函数名字不能冲突,
        //       名字相同时按顺序查找接口映射表中的接口,
        //       返回第一个匹配的接口函数对应的dispid
        T* pT = static_cast<T*> (this);
        TIH_ENTRY* pEntry = pT->GetTypeInfoHolder();
        HRESULT hr = DISP_E_UNKNOWNNAME;
        while (pEntry->ptih != NULL)
        {
            hr = pEntry->ptih->GetIDsOfNames(riid, rgszNames,
                cNames, lcid, rgdispid);
            if (SUCCEEDED(hr))
            {
                for (UINT i = 0; i < cNames; i++)
                {
                    rgdispid[i] |= pEntry->dispEncode;
                }
                return hr;
            }
            else if (hr != DISP_E_UNKNOWNNAME)
            {
                return hr;
            }
            pEntry++;
        }
        return DISP_E_UNKNOWNNAME;
    }

    STDMETHOD(Invoke)(DISPID dispidMember, REFIID riid,
        LCID lcid, WORD wFlags, DISPPARAMS* pdispparams,
        VARIANT* pvarResult, EXCEPINFO* pexcepinfo,
        UINT* puArgErr)
    {
        T* pT = static_cast<T*> (this);
        TIH_ENTRY* pEntry = pT->GetTypeInfoHolder();
        HRESULT hr = DISP_E_MEMBERNOTFOUND;
        if (dispidMember & INTERFACE_MASK)
        {
            // 函数id是编码过的,查找对应的接口进行调用,一般是脚本一类的动态调用
            while (pEntry->ptih != NULL)
            {
                if (pEntry->dispEncode == (dispidMember & INTERFACE_MASK))
                {
                    // 找到接口,调用并退出
                    hr = pEntry->ptih->Invoke((IDispatch*)(((DWORD)pT)+pEntry->offset),
                        (dispidMember & DISPID_MASK), riid, lcid,
                        wFlags, pdispparams, pvarResult,
                        pexcepinfo, puArgErr);
                    return hr;
                }
                pEntry++;
            }
        }
        else
        {
            // 函数id未编码,逐个接口进行尝试,一般是VC生成的接口类进行的静态调用
            // NOTE: 不同的接口,如果存在dispid相同的函数,
            //       请保证其函数参数个数或者参数类型或者返回值类型不要相同,
            //       否则可能会调用到错误的接口函数
            while (pEntry->ptih != NULL)
            {
                hr = pEntry->ptih->Invoke((IDispatch*)(((DWORD)pT)+pEntry->offset),
                    dispidMember, riid, lcid,
                    wFlags, pdispparams, pvarResult,
                    pexcepinfo, puArgErr);
                if (SUCCEEDED(hr))
                {
                    // 调用成功退出
                    return hr;
                }
                pEntry++;
            }
        }
        return DISP_E_MEMBERNOTFOUND;
    }
};

// 映射表宏定义,需要在组件的头文件中引用
#define BEGIN_MULTI_DISPATCH_MAP(CLS)     typedef CLS theDerived;     static theDerived::TIH_ENTRY* GetTypeInfoHolder() {     const DWORD _dwCnt = __COUNTER__;     static theDerived::TIH_ENTRY pDispEntries[] = {

// 函数id编码,占用id的高16位bit
#define MULTI_DISPATCH_ENCODE() (((DWORD)(__COUNTER__) - _dwCnt) << 16)

#define MULTI_DISPATCH_ENTRY(theBase) { &theBase::_tih, MULTI_DISPATCH_ENCODE(), offsetofclass(theBase, theDerived) },

#define END_MULTI_DISPATCH_MAP() { NULL, 0UL, 0UL } };     return(pDispEntries); }

#endif // sentry

使用方法,在组件类的头文件中,让我们的组件继承我们的类:

class ATL_NO_VTABLE CQMPlugin :
	public CComObjectRootEx<CComSingleThreadModel>,
	public CComCoClass<CQMPlugin, &CLSID_QMPlugin>,
	public ISupportErrorInfo,
        <span style="color:#3366ff;">public XMultiDispImpl<CQMPlugin>,</span>
	public IDispatchImpl<IQMPlugin, &IID_IQMPlugin, &LIBID_zdLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
        public IDispatchImpl<IQMPluginStandard, &IID_IQMPluginStandard, &LIBID_zdLib, /*wMajor =*/ 1, /*wMinor =*/ 0>

蓝色是要手动添加的代码

在BEGIN_COM_MAP和END_COM_MAP中添加如下代码:

BEGIN_COM_MAP(CQMPlugin)
    <span style="color:#3366ff;">COM_INTERFACE_ENTRY2(IDispatch, XMultiDispImpl<CQMPlugin>)</span>
    COM_INTERFACE_ENTRY(IQMPlugin)
    COM_INTERFACE_ENTRY(IQMPluginStandard)
    COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()

意思是说当外部程序查询IDispatch接口,返回我们实现的类的虚函数表

下面就是要添加接口映射表了,目前感觉这里还是看着不是很爽,暂时没有解决办法:

<span style="color:#3366ff;">typedef IDispatchImpl<IQMPlugin, &IID_IQMPlugin, &LIBID_zdLib, /*wMajor =*/ 1, /*wMinor =*/ 0> TQMPlugin;
typedef IDispatchImpl<IQMPluginStandard, &IID_IQMPluginStandard, &LIBID_zdLib, /*wMajor =*/ 1, /*wMinor =*/ 0> TQMPluginStandard;
BEGIN_MULTI_DISPATCH_MAP(CQMPlugin)
    MULTI_DISPATCH_ENTRY(TQMPlugin)
    MULTI_DISPATCH_ENTRY(TQMPluginStandard)
END_MULTI_DISPATCH_MAP()</span>

记住要先typedef 再用MULTI_DISPATCH_ENTRY,不然会编译失败,这也是让人不爽的地方。

其他的可以按正常的ATLCOM接口开发步骤进行开发了。

下面就是注意事项了:

1. 如果要在一个组件里实现多个disp接口,对于每个接口的方法或者属性,不要出现重名的情况,代码中有说明;

2. 函数的dispid可以相同,但是如果dispid相同,请一定让两个函数的参数个数,参数类型或者返加值类型不要全部相同,不然可能调用到错误的接口函数;

3. 理论上这个类实现的多接口是支持静态调用和动态调用的

4. 对于dispid相同的情况,代码是通过在dispid的高16bit设置标志还区别的,对于VBS一类的动态脚本调用是没有问题的,在脚本里可以把组件当成只实现了一个接口

5. 因为使用的dispid的高16bit,所以这个类最多支持65536个接口,同时每个接口的方法和属性不能超过65536个,有需要的可以自行在代码里调整。

最后希望代码能帮助到大家,没有什么比自己的代码被别人认可更让人。。。,找不到形容词了,欢迎大定留言哈。

时间: 2024-08-08 09:16:38

ATL实现一个组件多个dual接口,multidisp的相关文章

ITextSharp用来生成 PDF 的一个组件

iTextSharp 是用来生成  PDF 的一个组件,在 1998 年夏天的时候,Bruno Lowagie ,iText 的创作者,参与了学校的一个项目,当时使用 HTML 来生成报告,但是,使用 HTML 打印的效果很不理想.最后,他发现,使用 PDF 可以完美解决打印问题,为了能够在各个系统中使用,iText 组件库诞生了. 网页上面浏览pdf,目前一般是先转成swf格式,再查看. http://sourceforge.net/projects/itextsharp/files/

一个用js写的接口http调试程序

公司有非常多手机app的项目.手机app又要常常訪问后台提交与查询数据. 所曾经端app与后台的开发与測试过程中接口调试是一个常常要做的工作. 而每当出现一个BUG,前端appproject师与后台project师往往要相互合作才干定位bug到底在那里.而非本项目的人往往还难以帮上忙(必需要读懂别人写的程序,等等的.开发们都懂的...) 所以自己利用了业余时间用js+hta的方式实现了一个简单的接口调试程序.能够由非开发者对比接口文档就能够进行接口的调试和測试. 下面是程序界面 这是一个机票程序

初学React:定义一个组件

接着聊React,今天说说如何创建一个组件类. <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>React工程模板</title> <!-- react.js 是React的核心库 --> <script src="./build/react.js charset="

React.js第二天,优化第一天的最后一个组件

废话不多说,直接上代码了 <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title> <script src="javascripts/react.js"></script> <script src="javascripts/reac

一个依靠STL vector的接口进行申请和回收管理的内存池类( c++ 封装)

其他出现两次,只有一个出现一次的那道题我就不更了,直接抑或,最后的结果就是那个数.为什么可以这样做呢?因为一个32位int,如果所有数都出现了两次,那么为1的那些位统计的个数一定是2的倍数,抑或之后全变成0.一个数出现了一次,它为1的那些位上,1的个数必定是奇数,抑或之后一定还是1. 我之前知道出现两次这个题的解法,但是理解的不够深,以为抑或是关键,其实不是,出现了偶数次才是关键.理解了这点,推广到出现3次上,如果所有的出现了三次,那么为1的那些位1的个数一定是三的倍数,那如果有一个数出现了一次

怎么新开一个组件并且配置路由?vue-cli

首先要明白: 路由就是url路径,如果一个组件被引入到了另外一个组件,这个页面就包含这个组件了,所以这个被包含的组件不要去路由哪里配置了 第一步: 先写上想要添加的组件 2.组件的内容 3.路由的配置 ,index.js文件 4.在地址栏 原文地址:https://www.cnblogs.com/antyhouse/p/9069473.html

RESTful【第三章】:序列化组件的使用及接口设计

序列化组件的使用及接口设计 一.Django原生的serializer(序列化) 使用步骤: 1.导入模块 from django.core.serializers import serialize 2.获取queryset 3.对queryset进行序列化 4.将序列化后的数据,响应给客户端 实例: #1.导入Django自带的原生的序列化模块 from django.core.serializers import serialize class CourseView(APIView): de

Go“一个包含nil指针的接口不是nil接口”踩坑

最近在项目中踩了一个深坑--"Golang中一个包含nil指针的接口不是nil接口",总结下分享出来,如果你不是很理解这句话,那推荐认真看下下面的示例代码,避免以后写代码时踩坑. 示例一 先一起来看下这段代码,你感觉有没有问题呢? type IPeople interface { hello() } type People struct { } func (p *People) hello() { fmt.Println("github.com/meetbetter"

VC++ : VS2008 使用ATL开发COM组件

新建ATL Project,工程名命名为MyAtlCom: 出现工程 向导,一路"Next": Add class,点击添加 ATL Simple Object , 类名CStatistic, 接口IStatistic,"Next"到底; 打开类视图,可以看到ATLCOM下新增了CStatistic类和IStatistic接口: 在ISample上右键,Add->Add Method (或Add Property...)来丰富接口了,然后在CStatistic