聚合的概念
聚合源自組件重用,。當(dāng)有兩個(gè)組件A和B,他們分別實(shí)現(xiàn)了自己的接口IA和IB,。如果有一個(gè)客戶程序創(chuàng)建了A對(duì)象使得自己可以調(diào)用IA的方法,,但同時(shí)又想獲得IB的接口,調(diào)用IB的方法,。這時(shí)候有兩種做法:一種是客戶程序創(chuàng)建B對(duì)象,,還有一種方法是A組件內(nèi)部創(chuàng)建B組件,然后客戶通過(guò)某種途徑調(diào)用B的接口方法,。
第一種方法,,使得客戶必須知道有獨(dú)立的B組件的存在,第二種方法客戶可以認(rèn)為只有一個(gè)組件A,,組件A實(shí)現(xiàn)了兩個(gè)接口IA和IB,。第二種方法可以制造出一種假象,讓客戶程序編寫(xiě)更加簡(jiǎn)單,。從組件A如何管理組件B的方法上,,第二種方法還可以分為兩種:包容和聚合。
包容很簡(jiǎn)單,,如果組件IB接口擁有一個(gè)方法F(),那么A組件就要實(shí)現(xiàn)一個(gè)自己的IBEx接口,,并實(shí)現(xiàn)IBEx::F(
)方法,內(nèi)部調(diào)用IB::F()方法,。這樣,,客戶也就可以通過(guò)調(diào)用IBEx::F()來(lái)調(diào)用IB::F。在這種情況下,,客戶只知道有IA和IBEx接口,,不知道還存在另一個(gè)B組件和IB接口。IBEx::F()增加一些代碼從而修改IB::F()方法的功能,,甚至可以完全丟棄IB::F()方法,。
聚合通常用于IB接口的功能完全不需要做任何的修改,就可以直接交給用戶使用的情況,。這時(shí)候,,如果IB接口的方法很多,包容就顯得很笨拙,。因?yàn)樗坏貌粚?duì)每一個(gè)方法作一次包裝,,盡管什么都不做。COM+對(duì)象池就是通過(guò)聚合我們的組件,來(lái)把我們組件的接口暴露給客戶的,。聚合方式下,,A組件直接將IB接口交給客戶,客戶就可以調(diào)用,,但是客戶仍然以為是A組件實(shí)現(xiàn)了IB接口,。如下圖:
組件A
IUnknown
客戶直接使用
客戶直接使用
內(nèi)部組件的實(shí)現(xiàn)
客戶程序只知道A組件而不知道B組件,并且認(rèn)為A組件實(shí)現(xiàn)了IA和IB接口,。因此,,當(dāng)客戶創(chuàng)建了A組件(通過(guò)CoCreateInstance函數(shù)),獲取到IUnknown接口時(shí),,應(yīng)該獲得的是A組件實(shí)現(xiàn)的IUnknown接口,。
問(wèn)題在于B組件有自己的IUnknown的接口實(shí)現(xiàn),如果B組件還是采用一般的方法實(shí)現(xiàn)IUnknown接口的話,,當(dāng)客戶調(diào)用IB::QueryInterface函數(shù),,就不會(huì)得到IA接口,這當(dāng)然是不允許的,。所以一個(gè)支持聚合的組件,,它的IUnknown實(shí)現(xiàn)必然要有別于普通組件。
B組件應(yīng)該擁有一個(gè)成員變量IUnknown*
m_pUnknownOuter;該指針可以指向A組件的IUnknown接口,。當(dāng)客戶調(diào)用IB::QueryInterface請(qǐng)求時(shí),,如果IB接口已經(jīng)被聚合了,就調(diào)用m_pUnknownOuter->QueryInterface方法,,這實(shí)際上就是調(diào)用了A組件的QueryInterface方法,。那么,,m_pUnknownOuter指針是什么時(shí)候被初始化的呢,?CoCreateInstance函數(shù)和IClassFactory::CreateInstance方法都接受一個(gè)IUnknown參數(shù)。如果A組件內(nèi)部想聚合B組件的IB接口,,他就會(huì)將自己的IUnknown指針傳遞進(jìn)去,如果A組件并不想聚合B組件,,那么簡(jiǎn)單的傳遞一個(gè)NULL就行了。
B組件可以根據(jù)m_pUnknownOuter是否為NULL,,來(lái)判斷是否被聚合,。
B組件實(shí)現(xiàn)的具體步驟如下:
1) 聲明一個(gè)INondelegationUnknown接口,該接口擁有IUnknown接口一樣的純虛函數(shù),,當(dāng)然函數(shù)名前面均加上Nondelegation前綴,。
2) CB類(假設(shè)CB類為組件類)繼承并實(shí)現(xiàn)INondelegationUnknown接口,實(shí)現(xiàn)代碼和普通組件的IUnknown一樣,,這用于非聚合的情況下,。
3) CB類的構(gòu)造函數(shù)接受IUnknown指針,如果傳遞進(jìn)來(lái)的是NULL,說(shuō)明B組件并不被用作聚合。m_pUnknownOuter變量就指向B組件自身的IUnknown接口,。如果傳遞進(jìn)來(lái)的不是NULL,,說(shuō)明被用作聚合。m_pUnknownOuter變量值等于參數(shù)值,,指向外部組件的IUnknown接口指針,。
4) CB類也要繼承并實(shí)現(xiàn)IUnknown接口。這個(gè)接口的QueryInterface函數(shù)實(shí)現(xiàn)只是調(diào)用m_pUnknownOuter->QueryInterfac方法,。m_pUnknownOuter究竟代表什么取決于創(chuàng)建組件時(shí)是否傳遞了外部組件的IUnknown指針,。
5) B組件的類廠的CreateInstance方法內(nèi)部創(chuàng)建CB類時(shí),將IUnknown*
pUnkownOuter參數(shù)傳遞給構(gòu)造函數(shù),。創(chuàng)建成功后,,調(diào)用B組件自身的(而不是外部的)NondelegationQueryInterface方法,將自身的INondelegationUnknown傳遞出去給組件A,。
外部組件的實(shí)現(xiàn)
外部組件A要?jiǎng)?chuàng)建組件B的實(shí)例,,并保存B組件的INondelegationUnknown接口指針。該接口指針是通過(guò)上一節(jié)5)由B的類廠返回的,。m_pUnknownInner變量保存了B接口的INondelegationUnknown接口指針,。外部組件調(diào)用CoCreateInstance函數(shù)創(chuàng)建聚合組件時(shí),iid必須等于IID_IUnknown,。
外部組件要修改自己的QueryInterface,,當(dāng)客戶程序請(qǐng)求IB接口的時(shí)候,將調(diào)用m_pUnknownInner->QueryInterface方法,。
外部組件可以通過(guò)QueryInterface的代碼來(lái)控制是否聚合B組件所有的接口,。如果不想聚合B組件實(shí)現(xiàn)的IC接口??梢栽谳斎?yún)?shù)iid等于IID_IC的時(shí)候,,返回E_NOINTERFACE。
聚合B組件的所有接口的方式稱為盲聚合,。盲聚合的缺點(diǎn)是:如果B組件實(shí)現(xiàn)了IPersist接口,,客戶調(diào)用IPersist::GetClassID方法時(shí),獲得的是CLSID_CB,,這就暴露了內(nèi)部的B組件,;還有就是B組件實(shí)現(xiàn)的接口不了解A組件的狀態(tài),如果B組件實(shí)現(xiàn)了ISave接口,,客戶調(diào)用了它,,卻不能正確完成保存組件狀態(tài)的功能。所以,,一般我們要避免使用盲聚合,,而要有選擇的聚合,。
外部組件還有一個(gè)重要的任務(wù)就是控制內(nèi)部組件的生命周期。因?yàn)閮?nèi)部組件擁有外部組件的IUnknown指針,,這時(shí)候當(dāng)調(diào)用內(nèi)部組件的AddRef/Release,,改變的是外部組件的引用計(jì)數(shù)。所以如果需要減少引用計(jì)數(shù),,應(yīng)該調(diào)用pUnknowOuter(pUnknowOuter其實(shí)就是this指針的強(qiáng)制轉(zhuǎn)換)->Release(
),。CA類析構(gòu)時(shí),要采用以下特殊的做法保證不會(huì)被過(guò)早的析構(gòu)和多次釋放,。
m_cRef=1;
IUnknown* pUnknownOuter = this;
pUnknownOuter->AddRef( );
m_pIB->Release( );
需要注意的是CA的析構(gòu)函數(shù)是在A組件自身的引用計(jì)數(shù)為0時(shí)才會(huì)被調(diào)用,,如果沒(méi)有m_cRef=1這行代碼,m_pIB->Release(
)會(huì)導(dǎo)致析構(gòu)函數(shù)再次被調(diào)用,。
` 最后,,析溝函數(shù)將釋放組件B。通過(guò)調(diào)用B的INondelegationUnknown::Release(
)方法,。
代碼如下:
if(m_pUnknownInner!=NULL)
{
m_pUnknownInner->Release (
);
}
以上總結(jié)主要來(lái)自于<<COM技術(shù)內(nèi)幕>>,。但是在ATL中,一切都被隱藏的很多,。
ATL7對(duì)內(nèi)部組件的支持
使用ATL7.1創(chuàng)建ATL簡(jiǎn)單對(duì)象(傳統(tǒng)COM對(duì)象時(shí))向?qū)е锌梢赃x擇是否支持聚合,,默認(rèn)是支持的。我們現(xiàn)在開(kāi)始創(chuàng)建內(nèi)部組件,。
按照上圖步驟創(chuàng)建組件B,,并且實(shí)現(xiàn)了IB接口。現(xiàn)在我們?yōu)镮B增加方法F(
),。F()方法很簡(jiǎn)單,,只是彈出一個(gè)消息框。
STDMETHODIMP
CB::F(void)
{
MessageBox(NULL,"OK","OK",MB_OK);
return S_OK;
}
在ATL中,,類廠是由CComCoClass類的宏DECLARE_CLASSFACTORY()實(shí)現(xiàn)的,。該宏的定義為:#define
DECLARE_CLASSFACTORY()
DECLARE_CLASSFACTORY_EX(ATL::CComClassFactory)
CComClassFactory實(shí)現(xiàn)了IClassFactory接口。最主要的就是實(shí)現(xiàn)了創(chuàng)建組件的函數(shù),。
// 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, 0, _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是創(chuàng)建COM對(duì)象的函數(shù)的指針,。創(chuàng)建CB類的函數(shù)指針是被保存到對(duì)象映射表中的用于創(chuàng)建CB類的函數(shù)指針。對(duì)象映射表是一個(gè)_ATL_OBJMAP_ENTRY結(jié)構(gòu)數(shù)組,,該結(jié)構(gòu)數(shù)組還保存了創(chuàng)建類廠的函數(shù)指針。該結(jié)構(gòu)數(shù)組的初始化是通過(guò)B.h中的宏OBJECT_ENTRY_AUTO(__uuidof(B),
CB)來(lái)完成的,。該宏取代了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,該類的作用是使得組件類的生命周期和服務(wù)器生命周期相同,,所以適用于進(jìn)程內(nèi)組件,。CComCreator類只提供了一個(gè)CreateInstance函數(shù),,用來(lái)幫助我們創(chuàng)建一個(gè)組件類,這里就幫助我們創(chuàng)建類廠,。
所以宏DECLARE_CLASSFACTORY()的功能就是:
1) 通過(guò)從CComClassFactory類派生實(shí)現(xiàn)了IClassFactory接口
2) 使用CComObjectCached類指定了類廠對(duì)象的生命周期和服務(wù)器生命周期相同
3) 定義了_ClassFactoryCreatorClass類型,,該類型的CreateInstance函數(shù)可以創(chuàng)建類廠對(duì)象,而且是采用多步構(gòu)造的方式,防止了構(gòu)造期間的意外析構(gòu),。
該宏的定義如下:
#define DECLARE_AGGREGATABLE(x) public:/
typedef ATL::CComCreator2< ATL::CComCreator<
ATL::CComObject< x > >, ATL::CComCreator< ATL::CComAggObject< x
> > > _CreatorClass;
_CreatorClass類型的CreateInstance函數(shù)即可以創(chuàng)建獨(dú)立組件,,也可以創(chuàng)建聚合組件,取決于傳入的pOuterIUnknown參數(shù)是否為NULL,。
值得一提的是CComAggObject類通過(guò)兩次從CComObjectRootEx繼承,,實(shí)現(xiàn)了內(nèi)部組件的兩個(gè)IUnknown接口,一個(gè)是IUnknown*,,一個(gè)是INondelegationUnknown*(其實(shí)ATL中不使用INondelegationUnknown名字,,但是作用一樣和INondelegationUnknown一樣)。
如果我們?cè)谙驅(qū)е羞x擇不支持聚合,,向?qū)?huì)在我們的CB類內(nèi)加上宏#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函數(shù)當(dāng)接收到不為NULL的pOuterIUnknown參數(shù)時(shí)會(huì)失敗,。
所以,當(dāng)我們有了ATL的支持后,,我們創(chuàng)建內(nèi)部組件的一切工作只是在向?qū)е羞x擇支持聚合,,輕松愜意!
ATL7對(duì)外部組件的支持
現(xiàn)在我們另創(chuàng)建一個(gè)名為OuterComponent的ATL項(xiàng)目,,創(chuàng)建A組件并實(shí)現(xiàn)IA接口,。創(chuàng)建步驟就不再敘述。A組件是否支持聚合無(wú)關(guān)緊要,,我們這里就默認(rèn)支持吧,。在OuterComponent項(xiàng)目中導(dǎo)入InnerComponent.dll,然后將CComPtr<IUnknown>
m_pIB作為CA的成員變量,。請(qǐng)參考下面的代碼的注釋
// A.h : CA
的聲明
#pragma once
#include "resource.h" //
主符號(hào)
#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)//自動(dòng)聚合IB接口,,有了該宏就不需要在FinalConstruct函數(shù)中創(chuàng)建B對(duì)象了
END_COM_MAP()
DECLARE_GET_CONTROLLING_UNKNOWN()//創(chuàng)建虛函數(shù)IUnknown*
GetControllingUnknown(),CComContainedObject類會(huì)繼承并實(shí)現(xiàn)該函數(shù),,然后將返回最外層組件的IUnknown指針
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT
FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
m_spunkIB.Release();//避免兩次析構(gòu)內(nèi)部組件
}
private:
CComPtr<IUnknown>
m_spunkIB;//保存被聚合的內(nèi)部組件的IUnknown接口
};
OBJECT_ENTRY_AUTO(__uuidof(A), CA)
現(xiàn)在我們來(lái)用一下,,創(chuàng)建一個(gè)客戶程序,它導(dǎo)入A和B的組件類型庫(kù),,然后創(chuàng)建A組件,,然后查詢IB接口并調(diào)用IB::F()方法。
// Client.cpp :
定義控制臺(tái)應(yīng)用程序的入口點(diǎn),。
//
#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大大簡(jiǎn)化了我們對(duì)于聚合的使用。
|
|
來(lái)自: semo_zhang > 《com組件》