聚合,真正的聚合ActiveX控件 .

其实,个人以为,用聚合的方法来整合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。也懒得弄了,所以就这样了。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结