其实,个人以为,用聚合的方法来整合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,DISPID*);
STDMETHOD(Invoke)(DISPID,REFIID,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,DISPID* rgdispid)
{
METHOD_PROLOGUE_EX_(CTagcCtrl,Dispatchx)
HRESULT hr = ((LPDISPATCH)(&pThis->m_xDispatch))->GetIDsOfNames(
riid,
rgszNames,
cNames,
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,
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,wFlags,pDispParams,pvarResult,
pexcepinfo,puArgErr);
pd->Release();
return hr;
}
}
return DISP_E_MEMBERNOTFOUND;
}
return ((LPDISPATCH)&(pThis->m_xDispatch))->Invoke(
dispid,
riid,
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,VTS_NONE)
END_DISPATCH_MAP()
这里Hello为我定义的一个控件方法,一并列上了。
e.在接口映射表中添加IDispatch接口
BEGIN_INTERFACE_MAP(CTagcCtrl,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,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。也懒得弄了,所以就这样了。