聚合源自组件重用。当有两个组件A和B,他们分别实现了自己的接口IA和IB。如果有一个客户程序创建了A对象使得自己可以调用IA的方法,但同时又想获得IB的接口,调用IB的方法。这时候有两种做法:一种是客户程序创建B对象,还有一种方法是A组件内部创建B组件,然后客户通过某种途径调用B的接口方法。
第一种方法,使得客户必须知道有独立的B组件的存在,第二种方法客户可以认为只有一个组件A,组件A实现了两个接口IA和IB。第二种方法可以制造出一种假象,让客户程序编写更加简单。从组件A如何管理组件B的方法上,第二种方法还可以分为两种:包容和聚合。
包容很简单,如果组件IB接口拥有一个方法F(),那么A组件就要实现一个自己的IBEx接口,并实现IBEx::F( )方法,内部调用IB::F()方法。这样,客户也就可以通过调用IBEx::F()来调用IB::F。在这种情况下,客户只知道有IA和IBEx接口,不知道还存在另一个B组件和IB接口。IBEx::F()增加一些代码从而修改IB::F()方法的功能,甚至可以完全丢弃IB::F()方法。
聚合通常用于IB接口的功能完全不需要做任何的修改,就可以直接交给用户使用的情况。这时候,如果IB接口的方法很多,包容就显得很笨拙。因为它不得不对每一个方法作一次包装,尽管什么都不做。COM+对象池就是通过聚合我们的组件,来把我们组件的接口暴露给客户的。聚合方式下,A组件直接将IB接口交给客户,客户就可以调用,但是客户仍然以为是A组件实现了IB接口。如下图:
组件A
IUnkNown
组件B
|
IB
|
客户直接使用
IA
|
客户直接使用
客户程序只知道A组件而不知道B组件,并且认为A组件实现了IA和IB接口。因此,当客户创建了A组件(通过CoCreateInstance函数),获取到IUnkNown接口时,应该获得的是A组件实现的IUnkNown接口。
问题在于B组件有自己的IUnkNown的接口实现,如果B组件还是采用一般的方法实现IUnkNown接口的话,当客户调用IB::QueryInterface函数,就不会得到IA接口,这当然是不允许的。所以一个支持聚合的组件,它的IUnkNown实现必然要有别于普通组件。
B组件应该拥有一个成员变量IUnkNown* m_pUnkNownOuter;该指针可以指向A组件的IUnkNown接口。当客户调用IB::QueryInterface请求时,如果IB接口已经被聚合了,就调用m_pUnkNownOuter->QueryInterface方法,这实际上就是调用了A组件的QueryInterface方法。那么,m_pUnkNownOuter指针是什么时候被初始化的呢?CoCreateInstance函数和IClassFactory::CreateInstance方法都接受一个IUnkNown参数。如果A组件内部想聚合B组件的IB接口,他就会将自己的IUnkNown指针传递进去,如果A组件并不想聚合B组件,那么简单的传递一个NULL就行了。
B组件可以根据m_pUnkNownOuter是否为NULL,来判断是否被聚合。
B组件实现的具体步骤如下:
3) CB类的构造函数接受IUnkNown指针,如果传递进来的是NULL,说明B组件并不被用作聚合。m_pUnkNownOuter变量就指向B组件自身的IUnkNown接口。如果传递进来的不是NULL,说明被用作聚合。m_pUnkNownOuter变量值等于参数值,指向外部组件的IUnkNown接口指针。
4) CB类也要继承并实现IUnkNown接口。这个接口的QueryInterface函数实现只是调用m_pUnkNownOuter->QueryInterfac方法。m_pUnkNownOuter究竟代表什么取决于创建组件时是否传递了外部组件的IUnkNown指针。
5) B组件的类厂的CreateInstance方法内部创建CB类时,将IUnkNown* pUnkownOuter参数传递给构造函数。创建成功后,调用B组件自身的(而不是外部的)NondelegationQueryInterface方法,将自身的INondelegationUnkNown传递出去给组件A。
外部组件A要创建组件B的实例,并保存B组件的INondelegationUnkNown接口指针。该接口指针是通过上一节5)由B的类厂返回的。m_pUnkNownInner变量保存了B接口的INondelegationUnkNown接口指针。外部组件调用CoCreateInstance函数创建聚合组件时,iid必须等于IID_IUnkNown。
外部组件可以通过QueryInterface的代码来控制是否聚合B组件所有的接口。如果不想聚合B组件实现的IC接口。可以在输入参数iid等于IID_IC的时候,返回E_NOINTERFACE。
聚合B组件的所有接口的方式称为盲聚合。盲聚合的缺点是:如果B组件实现了IPersist接口,客户调用IPersist::GetClassID方法时,获得的是CLSID_CB,这就暴露了内部的B组件;还有就是B组件实现的接口不了解A组件的状态,如果B组件实现了ISave接口,客户调用了它,却不能正确完成保存组件状态的功能。所以,一般我们要避免使用盲聚合,而要有选择的聚合。
外部组件还有一个重要的任务就是控制内部组件的生命周期。因为内部组件拥有外部组件的IUnkNown指针,这时候当调用内部组件的AddRef/Release,改变的是外部组件的引用计数。所以如果需要减少引用计数,应该调用pUnkNowOuter(pUnkNowOuter其实就是this指针的强制转换)->Release( )。CA类析构时,要采用以下特殊的做法保证不会被过早的析构和多次释放。
m_cRef=1;
pUnkNownOuter->AddRef( );
m_pIB->Release( );
代码如下:
if(m_pUnkNownInner!=NULL)
{
m_pUnkNownInner->Release ( );
}
以上总结主要来自于<<COM技术内幕>>。但是在ATL中,一切都被隐藏的很多。
ATL7对内部组件的支持
STDMETHODIMP CB::F(void)
{
MessageBox(NULL,"OK",MB_OK);
return S_OK;
}
在
ATL中,类厂是由CComCoClass类的宏DECLARE_CLASSFACTORY()实现的。该宏的定义为:
#define
DECLARE_CLASSFACTORY() DECLARE_CLASSFACTORY_EX(ATL::CComClassFactory)
CComClassFactory实现了
IClassFactory接口。最主要的就是实现了创建组件的函数。
// IClassFactory
STDMETHOD(CreateInstance)(LPUNKNowN pUnkOuter,REFIID riid,void** ppvObj)
{
ATLASSERT(m_pfnCreateInstance != NULL);
HRESULT hRes = E_POINTER;
if (ppvObj != NULL)
{
*ppvObj = NULL;
// can‘t ask for anything other than IUnkNown when aggregating
if ((pUnkOuter != NULL) && !InlineIsEqualUnkNown(riid))
{
ATLTRACE(atlTraceCOM,_T("CComClassFactory: asked for non IUnkNown interface while creating an aggregated object"));
hRes = CLASS_E_NOAGGREGATION;
}
else
hRes = m_pfnCreateInstance(pUnkOuter,riid,ppvObj);
}
return hRes;
}
m_pfnCreateInstance是创建
COM对象的函数的指针。创建CB类的函数指针是被保存到对象映射表中的用于创建CB类的函数指针。对象映射表是一个_ATL_OBJMAP_ENTRY结构数组,该结构数组还保存了创建类厂的函数指针。该结构数组的初始化是通过B.h中的宏OBJECT_ENTRY_AUTO(__uuidof(B),CB)来完成的。该宏取代了ATL3中的BEGIN_OBJECT_MAP()/OBJECT_ENTRY()/END_OBJECT_MAP()宏。
宏
DECLARE_CLASSFACTORY_EX定义为
#define
DECLARE_CLASSFACTORY_EX(cf) typedef ATL::CComCreator< ATL::CComObjectCached< cf > > _ClassFactoryCreatorClass;
CComObjectCached类派生自CComClassFactory,该类的作用是使得组件类的生命周期和服务器生命周期相同,所以适用于进程内组件。CComCreator类只提供了一个CreateInstance函数,用来帮助我们创建一个组件类,这里就帮助我们创建类厂。
所以宏DECLARE_CLASSFACTORY()的功能就是:
1) 通过从
CComClassFactory类派生实现了IClassFactory接口
2) 使用
CComObjectCached类指定了类厂对象的生命周期和服务器生命周期相同
该宏的定义如下:
#define
DECLARE_AGGREGATABLE(x) public:/
typedef ATL::CComCreator2< ATL::CComCreator< ATL::CComObject< x > >,ATL::CComCreator< ATL::CComAggObject< x > > > _CreatorClass;
值得一提的是
CComAggObject类通过两次从CComObjectRootEx继承,实现了内部组件的两个IUnkNown接口,一个是IUnkNown*,一个是INondelegationUnkNown*(其实ATL中不使用INondelegationUnkNown名字,但是作用一样和INondelegationUnkNown一样)。
#define
DECLARE_NOT_AGGREGATABLE(x) public:/
typedef ATL::CComCreator2< ATL::CComCreator< ATL::CComObject< x > >,ATL::CComFailCreator<CLASS_E_NOAGGREGATION> > _CreatorClass;
ATL7对外部组件的支持
现在我们另创建一个名为OuterComponent的ATL项目,创建A组件并实现IA接口。创建步骤就不再叙述。A组件是否支持聚合无关紧要,我们这里就默认支持吧。在OuterComponent项目中导入InnerComponent.dll,然后将
CComPtr<IUnkNown> m_pIB作为CA的成员变量。请参考下面的代码的注释
// A.h : CA
的声明
#pragma
once
#include
"resource.h" //
主符号
#include
"OuterComponent.h"
// CA
class
ATL_NO_VTABLE CA :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CA,&CLSID_A>,
{
public
:
CA()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_A)
BEGIN_COM_MAP(CA)
COM_INTERFACE_ENTRY(IA)
COM_INTERFACE_ENTRY(Idispatch)
COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IB,m_spunkIB.p,CLSID_B)//
自动聚合IB接口,有了该宏就不需要在FinalConstruct函数中创建B对象了
END_COM_MAP()
DECLARE_GET_CONTROLLING_UNKNowN()//
创建虚函数IUnkNown* GetControllingUnkNown(),CComContainedobject类会继承并实现该函数,然后将返回最外层组件的IUnkNown指针
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
m_spunkIB.Release();//
避免两次析构内部组件
}
private
:
};
OBJECT_ENTRY_AUTO(__uuidof(A),CA)
// Client.cpp :
定义控制台应用程序的入口点。
//
#include
"stdafx.h"
#import
"../../OuterComponent/OuterComponent/debug/OuterComponent.tlb" no_namespace named_guids raw_interfaces_only
#import
"../../InnerComponent/InnerComponent/debug/InnerComponent.tlb" no_namespace named_guids raw_interfaces_only
int
_tmain(int argc,_TCHAR* argv[])
{
::CoInitialize(NULL);
CComPtr<IA> pA;
HRESULT hr=pA.CoCreateInstance(CLSID_A);
CComPtr<IB> pB;
hr=pA->QueryInterface(&pB);
pB->F();
pB.Release();
pA.Release();
::CoUninitialize();
return 0;
}
一切成功。
ATL大大简化了我们对于聚合的使用。
陈抒
2005-9-15
再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow