聚合,真正的聚合ActiveX控件 .
发布时间:2020-12-13 20:16:52 所属栏目:百科 来源:网络整理
导读:其实,个人以为,用聚合的方法来整合ActiveX控件并没有多少意思,还是用包容来得实在。不过,看到有许多人在msdn上问,而且也想看看聚合一个ActiveX控件的过程,就自己花了些时间用MFC来弄弄了。 1.建立一个普通的MFC ActiveX控件tagc 2. 缺省情况下,控件是
其实,个人以为,用聚合的方法来整合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。也懒得弄了,所以就这样了。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |