ATL实现一个组件多个dual接口,multidisp
最近想自己写个按键精灵的插件,因而接触到这个问题: 怎样在1个组件里实现两个自动化接口。 主要针对的ATL,MFC貌似没这个问题,具体MFC是怎样实现的自己没有深究。
按键精灵的插件会在1个组件里实现两个dispinterface,具体请看oleview工具截图:
刚开始对这个问题不理解,以为不是问题,自己用ATL尝试了几次,才发现不是那末回事,因而google之。
MSDN上是这么说的,看这里 ATL不提供任作甚将多个两重接口支持。IDispatch的单个实现。 但是,有几个已知的方法来手动合并接口,如创建包括创建1个新的对象,履行 QueryInterface 函数或使用嵌套的对象1个基于typeinfo的实现的单独 IDispatch 接口来创建 IDispatch 接口的模板选件类。 这些方法都有潜伏的命名空间冲突问题,和代码复杂性和可保护性。 建议不要创建多个双绑定接口。 虽然ATL不支持,但是上面也说了,还是有方法的,因而再google之,终究找到1篇相干问题的文章,里面说的很细,还提供了几种不同的方案:网址:https://www.sellsbrothers.com/posts/details/12657
自己比较喜欢第2和第3种方案,对照来讲,第3种方案比较容易理解和实现。
固然我是用的第3种方案的简单实现,没有从typeinfo接口再继承,还是自己实现了1个类,代理其实接口的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)
{
// 默许返回第1个接口的类型库
return pEntry->ptih->GetTypeInfo(itinfo,lcid,pptinfo);
}
return E_FAIL;
}
STDMETHOD(GetIDsOfNames)(REFIID riid,LPOLESTR* rgszNames,UINT cNames,DISPID* rgdispid)
{
// NOTE: 函数名字不能冲突,
// 名字相同时按顺序查找接口映照表中的接口,
// 返回第1个匹配的接口函数对应的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,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,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是编码过的,查找对应的接口进行调用,1般是脚本1类的动态调用
while (pEntry->ptih != NULL)
{
if (pEntry->dispEncode == (dispidMember & INTERFACE_MASK))
{
// 找到接口,调用并退出
hr = pEntry->ptih->Invoke((IDispatch*)(((DWORD)pT)+pEntry->offset),(dispidMember & DISPID_MASK),riid,wFlags,pdispparams,pvarResult,pexcepinfo,puArgErr);
return hr;
}
pEntry++;
}
}
else
{
// 函数id未编码,逐一接口进行尝试,1般是VC生成的接口类进行的静态调用
// NOTE: 不同的接口,如果存在dispid相同的函数,
// 请保证其函数参数个数或参数类型或返回值类型不要相同,
// 否则可能会调用到毛病的接口函数
while (pEntry->ptih != NULL)
{
hr = pEntry->ptih->Invoke((IDispatch*)(((DWORD)pT)+pEntry->offset),dispidMember,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,/*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,/*wMinor =*/ 0> TQMPlugin;
typedef IDispatchImpl<IQMPluginStandard,/*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. 如果要在1个组件里实现多个disp接口,对每一个接口的方法或属性,不要出现重名的情况,代码中有说明; 2. 函数的dispid可以相同,但是如果dispid相同,请1定让两个函数的参数个数,参数类型或返加值类型不要全部相同,不然可能调用到毛病的接口函数; 3. 理论上这个类实现的多接口是支持静态调用和动态调用的 4. 对dispid相同的情况,代码是通过在dispid的高16bit设置标志还区分的,对VBS1类的动态脚本调用是没有问题的,在脚本里可以把组件当做只实现了1个接口 5. 由于使用的dispid的高16bit,所以这个类最多支持65536个接口,同时每一个接口的方法和属性不能超过65536个,有需要的可以自行在代码里调剂。
最后希望代码能帮助到大家,没有甚么比自己的代码被他人认可更让人。。。,找不到形容词了,欢迎大定留言哈。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |