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.

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结