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;

}

/*上面的QueryInterface无法正常工作,问题并不在于上面的代码,而在于内部组件的IUnkNown接口,实际上它需要两个IUnkNown实现。客户可以得到两个IUnkNown接口,即内部组件的和外部组件的。这给客户带来一些混乱,因为每一个IUnkNown均将实现一个QueryInterface,而每一个QueryInterface均将分别支持一个不同的接口集。但客户应完全独立于聚合组件的实现,它不应该知道外部组件聚合了某个内部组件,并且永远不应看到内部组件的IUnkNown。前面说过,两个不同的接口当且仅当它们返回相同的IUnkNown指针时才会被认为是由同一组件实现的。因此,应将内部组件IUnkNown接口向客户隐藏起来,而只给他提供一个IUnkNown接口。内部组件的接口必须使用外部组件所实现的IUnkNown接口。此时外部组件的IUnkNown接口被称作是外部未知接口或控制未知接口。*/

/*内部组件使用外部组件最简单的方法是将调用请求转发给外部未知接口,为此,内部组件需要一个指向外部未知接口的指针,并且它还需要知道它是被聚合的。*/

外部未知接口:

HRESULT __stdcall CoCreateInstance(

const CLSID& clsid,

IUnkNown* pUnkNown,//Outer Component

DWORD dwClsConted,

const IID& iid,

void** ppv

);

HRESULT __stdcall IFactory::CreateInstance(IUnkNown* pIUnkNownOuter,

void** ppv

);


/*外部接口可以使用pUnkNown参数给内部组件传递其IUnkNown接口的指针,若此外部未知接口指针非空,表示我们想进行聚合。*/

/* 支持聚合,内部组件实际上将实现两个IUnkNown接口,其中的非代理未知接口将按通常的方式实现内部组件的IUnkNown接口,而代理未知接口将把IUnkNown成员函数调用转发给外部未知接口或非代理未知接口。*/

//非代理未知接口的实现(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;

}

//代理未知接口的实现:

/*代理未知接口只需将相应的调用请求转发给外部未知接口或非代理未知接口即可。下面给出一个支持聚合的组件的声明,此组件中包含一个名为m_pUnkNownOuter的指针。当此组件被聚合时,此指针指向外部未知接口,当此组件未被聚合时,此指针将指向非代理未知接口。当代理未知接口中的函数调用时,此调用将被转发到m_pUnkNownOuter指向的接口。*/

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函数(创建过程是由这个函数开始的),内部组件类厂的CreateInstance函数以及内部组件的构造函数。*/

一:外部组件的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.

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

相关文章

迭代器模式(Iterator)迭代器模式(Iterator)[Cursor]意图...
高性能IO模型浅析服务器端编程经常需要构造高性能的IO模型,...
策略模式(Strategy)策略模式(Strategy)[Policy]意图:定...
访问者模式(Visitor)访问者模式(Visitor)意图:表示一个...
命令模式(Command)命令模式(Command)[Action/Transactio...
生成器模式(Builder)生成器模式(Builder)意图:将一个对...