其實,個人以為,用彙總的方法來整合ActiveX控制項並沒有多少意思,還是用包容來得實在。不過,看到有許多人在msdn上問,而且也想看看彙總一個ActiveX控制項的過程,就自己花了些時間用MFC來弄弄了。
1.建立一個普通的MFC ActiveX控制項tagc
2. 預設情況下,控制項是從COleControl派生而來,所以已經有了自己的ActiveX控制項的實現和自己相應的介面映射,這樣 QueryInterface時,返回的ActiveX控制項的各種標準介面(IOleObject,IViewObject,IPersist等等)就都 是自己的,而不是將要彙總的控制項的了。個人以為至少有兩種方法來修改,一種是重載GetInterfaceHook,強制從被彙總的控制項中獲得 ActiveX控制項的標準介面,另一種是將控制項改成從CCmdTarget派生。本文採用第二種方法。
a.注釋掉OnDraw,DoPropExchange,OnResetState等COleControl特有的函數和
BEGIN_PROPPAGEIDS/END_PROPPAGEIDS 屬性頁面對宏,BEGIN_EVENT_MAP/END_EVENT_MAP事件映射宏, BEGIN_MESSAGE_MAP/END_MESSAGE_MAP訊息映射宏和BEGIN_DISPATCH_MAP / END_DISPATCH_MAP分發映射宏,當然還有它們相應的聲明DECLARE_XXXX。
b.將COleControl統統改成CCmdTarget。
c.新增成員變數
const IID* m_piidPrimary; // IID for control automation
const IID* m_piidEvents; // IID for control events
和成員函數
void CTagcCtrl::InitializeIIDs(const IID *piidPrimary, const IID *piidEvents)
{
m_piidPrimary = piidPrimary;
m_piidEvents = piidEvents;
EnableTypeLib();
}
這是因為可能會用到m_piidPrimary,而且還是調用一下EnableTypeLib()比較好。
這樣的話,建構函式中的 InitializeIIDs(&IID_DTagc, &IID_DTagcEvents);就不用注釋掉了。
d.建構函式和解構函式中分別加上 AfxOleLockApp();和 AfxOleUnlockApp();另外加上EnableAggregation();以使我們的控制項也可以被別人彙總。
CTagcCtrl::CTagcCtrl()
{
InitializeIIDs(&IID_DTagc, &IID_DTagcEvents);
// TODO: Initialize your control's instance data here.
m_lpAggrInner = NULL;
EnableAggregation();
AfxOleLockApp();
}
CTagcCtrl::~CTagcCtrl()
{
// TODO: Cleanup your control's instance data here.
AfxOleUnlockApp();
}
e.將我們要彙總的控制項的IUnknown介面指標儲存為我們控制項的成員變數,以備隨時使用
LPUNKNOWN m_lpAggrInner;
f.開始最重要的工作,重載OnCreateAggregates,在裡面建立我們所需要彙總的控制項,這裡我們使用TreeView控制項
BOOL CTagcCtrl::OnCreateAggregates()
{
CLSID clsid;
::CLSIDFromProgID( L"MSComctlLib.TreeCtrl.2", &clsid );
LPUNKNOWN pn = GetControllingUnknown();
CoCreateInstance(clsid,
pn, CLSCTX_INPROC_SERVER,
IID_IUnknown, (LPVOID*)&m_lpAggrInner);
if (m_lpAggrInner == NULL)
return FALSE;
return TRUE;
}
g.重載OnFinalRelease,在此時釋放掉所彙總的控制項
void CTagcCtrl::OnFinalRelease()
{
// TODO: Add your specialized code here and/or call the base class
if(m_lpAggrInner != NULL){
m_lpAggrInner->Release();
m_lpAggrInner = NULL;
}
CCmdTarget::OnFinalRelease();
}
h.在.h中添加介面映射聲明 DECLARE_INTERFACE_MAP(),在.cpp中添加介面映射定義,這裡只用INTERFACE_AGGREGATE添加了彙總介面,下面將會看到會出現問題。
BEGIN_INTERFACE_MAP(CTagcCtrl, CCmdTarget)
INTERFACE_AGGREGATE(CTagcCtrl, m_lpAggrInner)
END_INTERFACE_MAP()
i.如果現在編譯並測試的話,在ActiveX Control Test Container中將測試成功,但在VB中卻不行。
最後發現原因在於VB中上面代碼中的GetControllingUnknown()返回的是NULL。
這樣當請求(QueryInterface)我們控制項的一些標準介面如IOleObject等時因為有彙總介面的映射表,所以能找到介面,但是被彙總的控 件中的外部Unknown介面指標卻是NULL(因為pn = GetControllingUnknown() = NULL)。這樣就產生了問題。
這裡的問題是內部彙總的控制項AddRef時是增加自己的m_dwRef,而不是外部介面(即我們的控制項)的m_dwRef,可是對於控制項容器來說,它請求 的卻是我們的控制項的介面(它並不知道我們的控制項彙總了還是沒彙總,它只是請求某一個介面),要增加的是我們控制項的m_dwRef,結果就亂套了。
最終的原因是好象ActiveX Control Test Container中就是採用彙總的方式彙總我們的控制項的,不調用GetControllingUnknown(),直接用它的內部源碼,可以發現它其實是有m_pOuterUnknown的。
LPUNKNOWN CCmdTarget::GetControllingUnknown()
{
if (m_pOuterUnknown != NULL)
return m_pOuterUnknown; // aggregate of m_pOuterUnknown
LPUNKNOWN lpUnknown = (LPUNKNOWN)GetInterface(&IID_IUnknown);
return lpUnknown; // return our own IUnknown implementation
}
當注釋掉建構函式中的EnableAggregation();時,可以發現在ActiveX Control Test Container中也無法建立我們的控制項的。
j.所以最後的原因是介面映射表中沒有我們控制項自己的介面(包括IUnknown,挺有意思的吧,這就是MFC的CCmdTarget了,它是用包裹類來 實現各個介面的),我這裡先用了CCmdTarget中的m_xInnerUnknown來作為IUnknown介面,使GetInterface可以找 到一個IUnknown介面,也不知道是不是可以,沒仔細分析了,但是好象是沒問題了(當然EnableAggregation()還是要去掉注釋繼續調 用的,否則m_xInnerUnknown就是0了,還是沒有介面)。
BEGIN_INTERFACE_MAP(CTagcCtrl, CCmdTarget)
INTERFACE_PART(CTagcCtrl, IID_IUnknown, InnerUnknown)
INTERFACE_AGGREGATE(CTagcCtrl, m_lpAggrInner)
END_INTERFACE_MAP()
3.到目前為止,一個傀儡控制項已經建立成功了,它自己只有一個IUnknown介面,所有的介面都是它所彙總的控制項提供的。但是我們的目的顯然並不是如此,我們希望能夠擴充所彙總的控制項的功能。
當然我們可以為我們的控制項添加介面來擴充。
但是一般ActiveX控制項是通過Dispatch介面來提供它的功能的,我們怎麼辦呢,我們自己的控制項有自己的IDispatch介面,所彙總的控制項也有它自己的IDispatch介面,這兩個Dispatch介面的功能該怎樣合在一起呢?
很簡單,重新實現我們的IDispatch介面,先調用我們預設的IDispatch介面實現,如果沒找到正確的dispid,再調用我們所彙總的控制項的IDispatch介面就可以了。
我原來是想重載COleDispatchImpl來實現的,但是,出現了一些不可思議的錯誤,也懶得去查了,就改成自己新弄一個包裹類來提供 IDispatch,在裡面調用CCmdTarget的m_xDispatch(也就是COleDispatchImpl了)為我們提供的預設的 IDispatch實現和所彙總控制項的IDispatch介面。
a.在.h中用BEGIN_INTERFACE_PART/END_INTERFACE_PART宏添加包裹類的聲明
BEGIN_INTERFACE_PART(Dispatchx, IDispatch)
INIT_INTERFACE_PART(CTagcCtrl, Dispatchx)
STDMETHOD(GetTypeInfoCount)(UINT*);
STDMETHOD(GetTypeInfo)(UINT, LCID, LPTYPEINFO*);
STDMETHOD(GetIDsOfNames)(REFIID, LPOLESTR*, UINT, LCID, DISPID*);
STDMETHOD(Invoke)(DISPID, REFIID, LCID, WORD, DISPPARAMS*, LPVARIANT,
LPEXCEPINFO, UINT*);
END_INTERFACE_PART(Dispatchx)
b.實現XDispatchx類
ULONG FAR EXPORT CTagcCtrl::XDispatchx::AddRef()
{
METHOD_PROLOGUE(CTagcCtrl, Dispatchx)
return pThis->ExternalAddRef();
}
ULONG FAR EXPORT CTagcCtrl::XDispatchx::Release()
{
METHOD_PROLOGUE(CTagcCtrl, Dispatchx)
return pThis->ExternalRelease();
}
HRESULT FAR EXPORT CTagcCtrl::XDispatchx::QueryInterface(
REFIID iid, void FAR* FAR* ppvObj)
{
METHOD_PROLOGUE(CTagcCtrl, Dispatchx)
return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}
STDMETHODIMP CTagcCtrl::XDispatchx::GetTypeInfoCount(UINT* pctinfo)
{
METHOD_PROLOGUE_EX_(CTagcCtrl, Dispatchx)
*pctinfo = pThis->GetTypeInfoCount();
return S_OK;
}
STDMETHODIMP CTagcCtrl::XDispatchx::GetTypeInfo(UINT itinfo, LCID lcid,
LPTYPEINFO* pptinfo)
{
METHOD_PROLOGUE_EX_(CTagcCtrl, Dispatchx)
return ((LPDISPATCH)(&pThis->m_xDispatch))->GetTypeInfo(
itinfo,
lcid,
pptinfo);
/* ASSERT_POINTER(pptinfo, LPTYPEINFO);
if (itinfo != 0)
return E_INVALIDARG;
IID iid;
if (!pThis->GetDispatchIID(&iid))
return E_NOTIMPL;
return pThis->GetTypeInfoOfGuid(lcid, iid, pptinfo);*/
}
STDMETHODIMP CTagcCtrl::XDispatchx::GetIDsOfNames(
REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgdispid)
{
METHOD_PROLOGUE_EX_(CTagcCtrl, Dispatchx)
HRESULT hr = ((LPDISPATCH)(&pThis->m_xDispatch))->GetIDsOfNames(
riid,
rgszNames,
cNames,
lcid,
rgdispid);
if (rgdispid[0] == DISPID_UNKNOWN)
{
LPDISPATCH pd;
if(pThis->m_lpAggrInner){
pThis->m_lpAggrInner->QueryInterface(IID_IDispatch, (void**)&pd);
if(pd){
HRESULT hr = pd->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid);
pd->Release();
return hr;
}
}
}
return hr;
}
STDMETHODIMP CTagcCtrl::XDispatchx::Invoke(
DISPID dispid, REFIID riid, LCID lcid,
WORD wFlags, DISPPARAMS* pDispParams, LPVARIANT pvarResult,
LPEXCEPINFO pexcepinfo, UINT* puArgErr)
{
METHOD_PROLOGUE_EX_(CTagcCtrl, Dispatchx)
const AFX_DISPMAP_ENTRY* pEntry = pThis->GetDispEntry(dispid);
if (pEntry == NULL)
{
LPDISPATCH pd;
if(pThis->m_lpAggrInner){
pThis->m_lpAggrInner->QueryInterface(IID_IDispatch, (void**)&pd);
if(pd){
HRESULT hr = pd->Invoke(dispid, riid, lcid, wFlags, pDispParams,pvarResult,
pexcepinfo, puArgErr);
pd->Release();
return hr;
}
}
return DISP_E_MEMBERNOTFOUND;
}
return ((LPDISPATCH)&(pThis->m_xDispatch))->Invoke(
dispid,
riid,
lcid,
wFlags,
pDispParams,
pvarResult,
pexcepinfo,
puArgErr);
}
另外重載GetDispatchIID以使GetTypeInfo可以調用成功
BOOL CTagcCtrl::GetDispatchIID(IID *pIID)
{
if (m_piidPrimary != NULL)
*pIID = *m_piidPrimary;
return (m_piidPrimary != NULL);
}
c.在建構函式中添加EnableAutomation,給m_xDispatch賦給正確的COleDispatchImpl的vtbl,現在的建構函式如下:
CTagcCtrl::CTagcCtrl()
{
InitializeIIDs(&IID_DTagc, &IID_DTagcEvents);
// TODO: Initialize your control's instance data here.
m_lpAggrInner = NULL;
EnableAutomation();
EnableAggregation();
AfxOleLockApp();
}
d.取消分發介面的聲明和定義宏的注釋
// Dispatch maps
//{{AFX_DISPATCH(CTagcCtrl)
afx_msg void Hello();
//}}AFX_DISPATCH
DECLARE_DISPATCH_MAP()
BEGIN_DISPATCH_MAP(CTagcCtrl, CCmdTarget)
//{{AFX_DISPATCH_MAP(CTagcCtrl)
DISP_FUNCTION(CTagcCtrl, "Hello", Hello, VT_EMPTY, VTS_NONE)
//}}AFX_DISPATCH_MAP
DISP_FUNCTION_ID(CTagcCtrl, "AboutBox", DISPID_ABOUTBOX, AboutBox, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()
這裡Hello為我定義的一個控制項方法,一併列上了。
e.在介面映射表中添加IDispatch介面
BEGIN_INTERFACE_MAP(CTagcCtrl, CCmdTarget)
INTERFACE_PART(CTagcCtrl, IID_IUnknown, InnerUnknown)
INTERFACE_PART(CTagcCtrl, IID_IDispatch, Dispatchx)
INTERFACE_AGGREGATE(CTagcCtrl, m_lpAggrInner)
END_INTERFACE_MAP()
當然你也可以通過GetInterfaceHook添加IDispatch介面,如下:
LPUNKNOWN CTagcCtrl::GetInterfaceHook(const void *piid)
{
if(_AfxIsEqualGUID(IID_IDispatch, *(IID*)piid) ||
_AfxIsEqualGUID(*m_piidPrimary, *(IID*)piid) ){
return &m_xDispatchx;
}
return NULL;
}
這裡*m_piidPrimary即為IID_DTagc,這樣我們也可以通過IID_DTagc來獲得IDispatch介面了。
f.基本就是這樣了,編譯測試吧
g.雖然正常,不過在ActiveX Control Test Container中測試的話,會發現只有彙總控制項的Dispatch方法和屬性,這是為什麼呢,這是因為ActiveX Control Test Container是通過查詢控制項的IProvideClassInfo來獲得ITypeInfo介面指標,從而獲得類型資訊,很顯然,這裡的IProvideClassInfo介面指標來自所彙總的控制項。獲得的類型資訊自然是所彙總的控制項的類型資訊了。雖然改成自己的控制項的類型資訊後也沒什麼好處(因為就顯示不了被彙總控制項的類型資訊了),但是還是改一改吧。
用同樣的方法增加包裹類聲明XProvideClassInfo
BEGIN_INTERFACE_PART(ProvideClassInfo, IProvideClassInfo2)
INIT_INTERFACE_PART(CTagcCtrl, ProvideClassInfo)
STDMETHOD(GetClassInfo)(LPTYPEINFO* ppTypeInfo);
STDMETHOD(GetGUID)(DWORD dwGuidKind, GUID* pGUID);
END_INTERFACE_PART(ProvideClassInfo)
實現包裹類
/////////////////////////////////////////////////////////////////////////////
// CTagcCtrl::XProvideClassInfo
STDMETHODIMP_(ULONG) CTagcCtrl::XProvideClassInfo::AddRef()
{
METHOD_PROLOGUE_EX_(CTagcCtrl, ProvideClassInfo)
return (ULONG)pThis->ExternalAddRef();
}
STDMETHODIMP_(ULONG) CTagcCtrl::XProvideClassInfo::Release()
{
METHOD_PROLOGUE_EX_(CTagcCtrl, ProvideClassInfo)
return (ULONG)pThis->ExternalRelease();
}
STDMETHODIMP CTagcCtrl::XProvideClassInfo::QueryInterface(
REFIID iid, LPVOID* ppvObj)
{
METHOD_PROLOGUE_EX_(CTagcCtrl, ProvideClassInfo)
return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}
STDMETHODIMP CTagcCtrl::XProvideClassInfo::GetClassInfo(
LPTYPEINFO* ppTypeInfo)
{
METHOD_PROLOGUE_EX(CTagcCtrl, ProvideClassInfo)
CLSID clsid;
pThis->GetClassID(&clsid);
return pThis->GetTypeInfoOfGuid(GetUserDefaultLCID(), clsid, ppTypeInfo);
}
STDMETHODIMP CTagcCtrl::XProvideClassInfo::GetGUID(DWORD dwGuidKind,
GUID* pGUID)
{
METHOD_PROLOGUE_EX_(CTagcCtrl, ProvideClassInfo)
if (dwGuidKind == GUIDKIND_DEFAULT_SOURCE_DISP_IID)
{
IProvideClassInfo2* pinfo = NULL;
if(pThis->m_lpAggrInner != NULL){
pThis->m_lpAggrInner->QueryInterface(IID_IProvideClassInfo2, (void**)&pinfo);
pinfo->GetGUID(dwGuidKind, pGUID);
pinfo->Release();
}
else{
*pGUID = *pThis->m_piidEvents;
}
return NOERROR;
}
else
{
*pGUID = GUID_NULL;
return E_INVALIDARG;
}
}
添加介面映射
BEGIN_INTERFACE_MAP(CTagcCtrl, CCmdTarget)
INTERFACE_PART(CTagcCtrl, IID_IUnknown, InnerUnknown)
INTERFACE_PART(CTagcCtrl, IID_IDispatch, Dispatch)
INTERFACE_PART(CTagcCtrl, IID_IProvideClassInfo, ProvideClassInfo)
INTERFACE_PART(CTagcCtrl, IID_IProvideClassInfo2, ProvideClassInfo)
INTERFACE_AGGREGATE(CTagcCtrl, m_lpAggrInner)
END_INTERFACE_MAP()
在ActiveX Control Test Container中測試的話,可以發現將列出自己控制項的屬性和方法。在VB中始終出現的是自己控制項的屬性和方法,估計是因為VB是直接自己從typelib中得到的緣故吧。
g.令人遺憾的是,目前還想不到整合兩個控制項的TypeLib到一起的方法,看了半天odl和idl的資料,還是不知道。也許可以做個工具從typelib中反嚮導出odl的屬性和方法代碼文字,或者自己調用CreateTypeLib來產生自己的TypeLib。也懶得弄了,所以就這樣了。