天天看點

COM學習筆記(十):聚合

聚合的實作:假定客戶向外部元件請求接口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.

//下一篇将給出一個完整的例子。

繼續閱讀