COM聚合
发布时间:2020-12-14 05:03:25 所属栏目:百科 来源:网络整理
导读:? ? COM 聚合 ... 1 聚合的概念 ... 1 内部组件的实现 ... 2 外部组件的实现 ... 14 ATL7 对内部组件的支持 ... 33 分析: ... 54 DECLARE_CLASSFACTORY() 宏 ------------- 55 DECLARE_AGGREGATABLE(T) 宏 --------------------- 87 ATL7 对外部组件的支持
?
?
COM聚合
聚合的概念
内部组件的实现
外部组件的实现
ATL7对内部组件的支持
分析:
DECLARE_CLASSFACTORY()宏-------------
DECLARE_AGGREGATABLE(T)宏---------------------
ATL7对外部组件的支持
?
聚合的概念
聚合源自组件重用。当有两个组件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
?
?
?
?
客户直接使用
?
?
?
客户直接使用
?
内部组件的实现
?????? 客户程序只知道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组件实现的具体步骤如下:
1)?声明一个INondelegationUnknown接口,该接口拥有IUnknown接口一样的纯虚函数,当然函数名前面均加上Nondelegation前缀。
2)?CB类(假设CB类为组件类)继承并实现INondelegationUnknown接口,实现代码和普通组件的IUnknown一样,这用于非聚合的情况下。
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,当客户程序请求IB接口的时候,将调用m_pUnknownInner->QueryInterface方法。
?????? 外部组件可以通过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;
?????? IUnknown* pUnknownOuter = this;
?????? pUnknownOuter->AddRef( );
?????? m_pIB->Release( );
需要注意的是CA的析构函数是在A组件自身的引用计数为0时才会被调用,如果没有m_cRef=1这行代码,m_pIB->Release( )会导致析构函数再次被调用。
`
????? 最后,析沟函数将释放组件B。通过调用B的INondelegationUnknown::Release( )方法。
代码如下:
?????? if(m_pUnknownInner!=NULL)
{
?????? m_pUnknownInner->Release ( );
}
?
以上总结主要来自于<<COM技术内幕>>。但是在ATL中,一切都被隐藏的很多。
ATL7对内部组件的支持
?????? ?使用ATL7.1创建ATL简单对象(传统COM对象时)向导中可以选择是否支持聚合,默认是支持的。我们现在开始创建内部组件。
?
?
???
?
?
??????
?
??????
?
??????
?
?????? 按照上图步骤创建组件B,并且实现了IB接口。现在我们为IB增加方法F( )。F()方法很简单,只是弹出一个消息框。
??????
STDMETHODIMP CB::F(void)
{
???? ???? MessageBox(NULL,"OK",MB_OK);
???? ???? return S_OK;
}
?
分析:
DECLARE_CLASSFACTORY()宏-------------
在
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类指定了类厂对象的生命周期和服务器生命周期相同
3)?定义了
_ClassFactoryCreatorClass类型,该类型的CreateInstance函数可以创建类厂对象,而且是采用多步构造的方式,防止了构造期间的意外析构。
?
?
DECLARE_AGGREGATABLE(T)宏---------------------
该宏的定义如下:
#define
DECLARE_AGGREGATABLE(x) public:/
???? typedef ATL::CComCreator2< ATL::CComCreator< ATL::CComObject< x > >,ATL::CComCreator< ATL::CComAggObject< x > > > _CreatorClass;
??? _CreatorClass类型的
CreateInstance函数即可以创建独立组件,也可以创建聚合组件,取决于传入的pOuterIUnknown参数是否为NULL。
值得一提的是
CComAggObject类通过两次从CComObjectRootEx继承,实现了内部组件的两个IUnknown接口,一个是IUnknown*,一个是INondelegationUnknown*(其实ATL中不使用INondelegationUnknown名字,但是作用一样和INondelegationUnknown一样)。
如果我们在向导中选择不支持聚合,向导将会在我们的CB类内加上宏
#define
DECLARE_NOT_AGGREGATABLE(x)
#define
DECLARE_NOT_AGGREGATABLE(x) public:/
???? typedef ATL::CComCreator2< ATL::CComCreator< ATL::CComObject< x > >,ATL::CComFailCreator<CLASS_E_NOAGGREGATION> > _CreatorClass;
这样
_CreatorClass类型的CreateInstance函数当接收到不为NULL的pOuterIUnknown参数时会失败。
?
所以,当我们有了
ATL的支持后,我们创建内部组件的一切工作只是在向导中选择支持聚合,轻松惬意!
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 IDispatchImpl<IA,&IID_IA,&LIBID_OuterComponentLib,/*wMajor =*/ 1,/*wMinor =*/ 0>
{
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
:
???? CComPtr<IUnknown> m_spunkIB;//
保存被聚合的内部组件的IUnknown接口
};
OBJECT_ENTRY_AUTO(__uuidof(A),CA)
?
???
??? 现在我们来用一下,创建一个客户程序,它导入
A和B的组件类型库,然后创建A组件,然后查询IB接口并调用IB::F()方法。
?
// 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 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |