天天看点

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.

//下一篇将给出一个完整的例子。

继续阅读