ATL實現一個組件多個dual接口,multidisp
來源:程序員人生 發布時間:2014-12-14 08:56:29 閱讀次數:2689次
最近想自己寫個按鍵精靈的插件,因而接觸到這個問題: 怎樣在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, LCID lcid, 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, 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是編碼過的,查找對應的接口進行調用,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, lcid,
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, 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. 如果要在1個組件里實現多個disp接口,對每一個接口的方法或屬性,不要出現重名的情況,代碼中有說明;
2. 函數的dispid可以相同,但是如果dispid相同,請1定讓兩個函數的參數個數,參數類型或返加值類型不要全部相同,不然可能調用到毛病的接口函數;
3. 理論上這個類實現的多接口是支持靜態調用和動態調用的
4. 對dispid相同的情況,代碼是通過在dispid的高16bit設置標志還區分的,對VBS1類的動態腳本調用是沒有問題的,在腳本里可以把組件當做只實現了1個接口
5. 由于使用的dispid的高16bit,所以這個類最多支持65536個接口,同時每一個接口的方法和屬性不能超過65536個,有需要的可以自行在代碼里調劑。
最后希望代碼能幫助到大家,沒有甚么比自己的代碼被他人認可更讓人。。。,找不到形容詞了,歡迎大定留言哈。
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈