聚合的實作:假定客戶向外部元件請求接口IY,此時外部元件可以不實作IY接口,而隻需讓内部元件請求查詢ciIY接口并将此接口指針傳回給客戶。客戶 可以直接使用此指針來調用内部元件所實作的那些IY成員函數。此時就IY接口而言,外部元件相當于是被架空了:它放棄了對IY接口的控制而将此控制交給了内部元件。
聚合的關鍵是QueryInterface函數。
//下面是實作了接口IX并通過聚合提供IY接口的一個外部元件的聲明。
class CA : public IX{
public:
virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
virtual void __stdcall Fx(){cout<<"Fx"<<endl;}
CA();
~CA();
HRESULT Init();
private:
long m_cRef;
IUnknown* m_pUnknownInner;
};
//外部元件實際上使用的是内部元件對接口IY的實作,這一點是在其QueryInterface函數中完成的。
HRESULT __stdcall CA::QueryInterface(const IID& iid,void** ppv){
if(iid == IID_IUnknown) *ppv = static_cast<IX*>(this);
else if(iid == IID_IX) *ppv = static_cast<IX*>(this);
else if(iid == IID_IY) return m_pUnknownInner->QueryInterface(iid,ppv);
else{
*ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
外部未知接口:
HRESULT __stdcall CoCreateInstance(
const CLSID& clsid,
IUnknown* pUnknown, //Outer Component
DWORD dwClsConted,
const IID& iid,
void** ppv
);
HRESULT __stdcall IFactory::CreateInstance(IUnknown* pIUnknownOuter,
const IID& iid,
void** ppv
);
//非代理未知接口的實作(AddRef和Relese不變,QueryInterface有些細微卻重要的修改):
struct INondelegatingUnknown{
virtual HRESULT __stdcall NondelegatingQueryInterface(const IID&,void **) = 0;
virtual ULONG _stdcall NondelegatingAddRef() = 0;
virtual ULONG _stdcall NondelegatingRelease() = 0;
};
HRESULT __stdcall NondelegatingQueryInterface(const IID& iid,void** ppv){
if(iid == IID_IUnknown){
*ppv = static_cast<INodelegatingUnknown*>(this);
}
else if(iid == IID_IY){
*ppv = static_cast<IY*>(this);
}
else{
*ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
//代理未知接口的實作:
class CB: public IY,public INondelegatingUnknown{
public:
virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv){
return m_pUnknown->QueryInterface(iid,ppv);
}
virtual ULONG __stdcall AddRef(){
return m_pUnknownOuter->AddRef();
}
virtual ULONG __stdcall Release(){
return m_pUnknownOuter->Release(); //非代理未知接口沒有AddRef和Release阿!???
}
virtual HRESULT __stdcall NondelegatingQueryInterface(const IID& iid,void** ppv);
virtual ULONG __stdcall NonedelegatingAddRef();
virtual ULONG __stdcall NonedelegatingRelease();
virtual void __stdcall Fy(){cout<<"Fy"<<endl;}
CB(IUnknown* m_pUnknownOuter);
~CB();
private:
long m_cRef;
IUnknown* m_pUnknownOuter;
};
内部元件的建立:
一:外部元件的Init函數
HRESULT __stdcall CA::Init(){
IUnknown* pUnknownOuter = this;
HRESULT hr = CoCreateInstance(CLSID_Component2,
pUnknownOuter,
CLSCTX_INPROC_SERVER,
IID_IUnknown,(void**)&m_pUnknownInner
);
if(FAILED(hr)) return E_FAIL;
return S_OK;
}
外部元件的IClassFactory::CreateInstance将調用CA::Init。其IClassFactory實作保持不變,但内部元件的類廠需要一些修改。
二:内部元件的IClassFactory:CreateInstance函數
在被聚合的情況下,内部元件的IClassFactory元件必須使用INondelegatingUnknown接口而不能再使用IUnknown。
注意:當pUnknownOuter非空時(外部元件想聚合),IClassFactory:CreateInstance并不會失敗。但當iid不是IID_IUnknown時,CreateInstance必須失敗。當一個元件被聚合時,此内部元件将隻能傳回一個IUnknown接口,這是由于外部元件在其他時候無法擷取非代理未知接口的指針(因QueryInterface調用将被轉發到外部未知接口)。
HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnknownOuter,const IID& iid,void** ppv){
if((pUnknownOuter != NULL) && (iid != IID_IUnknown)) {
return CLASS_E_NOAGGREGATION;
}
CB* pB = new CB(pUnknownOuter);
if(pB == NULL){
return E_OUTOFMEMORY;
}
HRESULT hr = pB->NondelegatingQueryInterface(iid,ppv);
pB->NondelegatingRelease();
return hr;
}
上面代碼中的CreateInstance函數将調用NondelegatingQueryInterface而非QueryInterface來擷取新建立的内部元件中客戶所請求的接口。當内部元件被聚合時,它将把QueryInterface調用轉發給外部未知接口。
三:内部元件的構造函數
CB::CB(IUnknown* pUnknownOuter):m_cRef(1){
::InterlockedIncrement(&g_cComponents);
if(pUnknownOuter == NULL){//元件不被聚合
m_pUnknownOuter = reinterpret_cast<IUnknown*>(static_cast<INondelegatingIUnknown*>(this));
}
else{
m_pUnknownOuter = pUnknownOuter;
}
}
下面給出的是請求IY接口的CA::Init函數的實作:
HRESULT __stdcall CA::Init(){
IUnknown* pUnknownOuter = this;
HRESULT hr = ::CoCreateInstance(CLSID_Component2,
pUnknownOuter,
CLSCTX_INPROC_SERVER,
IID_IUnknown,
(void**)&m_pUnknownInner
);
if(FAILED(hr)){
return E_FAIL;
}
hr = m_pUnknownInner->QueryInterface(IID_IY,(void**)&m_pIY);
if(FAILED(hr)){
m_pUnknownInner->Release();
return E_FAIL;
}
pUnknownOuter->Release();
return S_OK;
}
在實作QueryInterface時可以有兩種不同選擇:
else if(iid == IID_IY){
return m_pUnknownInner->QueryInterface(iid,ppv);
}
或:
else if(iid == IID_IY){
*ppv = m_pIY;
}
剩下來的一大問題是釋放外部元件中指向内部元件的接口指針,此接口沒有被進行引用計數。這是一個三步的過程。首先,需要確定元件不會試圖再次将其自己釋放。其次,需要對外部元件調用AddRef,這是由于對内部元件的Release調用将會導緻對外部元件的Release調用。最後才可以将外部元件釋放。
m_cRef = 1; // 第一步 ,将引用計數設為1
IUnknown* pUnknownOuter = this; // 第二步
pUnknownOuter->AddRef();// 引用計數增大為2
m_pIY->Release(); // 第三步,内部元件将Release調用轉發給外部元件,外部元件引用計數由2->1.
//下一篇将給出一個完整的例子。