COM聚合(转)

来源:互联网 发布:mp3文件剪切器 mac 编辑:程序博客网 时间:2024/04/28 00:22

聚合的概念
聚合源自组件重用。当有两个组件A和B,他们分别实现了自己的接口IA和IB。如果有一个客户程序创建了A对象使得自己可以调用IA的方法,但同时又想获得IB的接口,调用IB的方法。这时候有两种做法:一种是客户程序创建B对象,还有一种方法是A组件内部创建B组件,然后客户通过某种途径调用B的接口方法。
第一种方法,使得客户必须知道有独立的B组件的存在,第二种方法客户可以认为只有一个组件A,组件A实现了两个接口IA和IB。第二种方法可以制造出一种假象,让客户程序编写更加简单。从组件A如何管理组件B的方法上,第二种方法还可以分为两种:包容和聚合。
包容很简单,如果组件IB接口拥有一个方法F(),那么A组件就要实现一个自己的IBEx接口,并实现IBEx::F( )方法,内部调用IB::F()方法。这样,客户也就可以通过调用IBEx::F()来调用IB::F。在这种情况下,客户只知道有IA和IBEx接口,不知道还存在另一个B组件和IB接口。IBEx::F()增加一些代码从而修改IB::F()方法的功能,甚至可以完全丢弃IB::F()方法。
聚合通常用于IB接口的功能完全不需要做任何的修改,就可以直接交给用户使用的情况。这时候,如果IB接口的方法很多,包容就显得很笨拙。因为它不得不对每一个方法作一次包装,尽管什么都不做。COM+对象池就是通过聚合我们的组件,来把我们组件的接口暴露给客户的。聚合方式下,A组件直接将IB接口交给客户,客户就可以调用,但是客户仍然以为是A组件实现了IB接口。如下图:
                              组件A
                                      IUnknown

       组件B
IB
 

 
 
 


客户直接使用
 
 

IA
 

 

客户直接使用
 
内部组件的实现
       客户程序只知道A组件而不知道B组件,并且认为A组件实现了IA和IB接口。因此,当客户创建了A组件(通过CoCreateInstance函数),获取到IUnknown接口时,应该获得的是A组件实现的IUnknown接口。      问题在于B组件有自己的IUnknown的接口实现,如果B组件还是采用一般的方法实现IUnknown接口的话,当客户调用IB::QueryInterface函数,就不会得到IA接口,这当然是不允许的。所以一个支持聚合的组件,它的IUnknown实现必然要有别于普通组件。
       B组件应该拥有一个成员变量IUnknown* m_pUnknownOuter;该指针可以指向A组件的IUnknown接口。当客户调用IB::QueryInterface请求时,如果IB接口已经被聚合了,就调用m_pUnknownOuter->QueryInterface方法,这实际上就是调用了A组件的QueryInterface方法。那么,m_pUnknownOuter指针是什么时候被初始化的呢?CoCreateInstance函数和IClassFactory::CreateInstance方法都接受一个IUnknown参数。如果A组件内部想聚合B组件的IB接口,他就会将自己的IUnknown指针传递进去,如果A组件并不想聚合B组件,那么简单的传递一个NULL就行了。
       B组件可以根据m_pUnknownOuter是否为NULL,来判断是否被聚合。
       B组件实现的具体步骤如下:
1) 声明一个INondelegationUnknown接口,该接口拥有IUnknown接口一样的纯虚函数,当然函数名前面均加上Nondelegation前缀。
2) CB类(假设CB类为组件类)继承并实现INondelegationUnknown接口,实现代码和普通组件的IUnknown一样,这用于非聚合的情况下。
3) CB类的构造函数接受IUnknown指针,如果传递进来的是NULL,说明B组件并不被用作聚合。m_pUnknownOuter变量就指向B组件自身的IUnknown接口。如果传递进来的不是NULL,说明被用作聚合。m_pUnknownOuter变量值等于参数值,指向外部组件的IUnknown接口指针。
4) CB类也要继承并实现IUnknown接口。这个接口的QueryInterface函数实现只是调用m_pUnknownOuter->QueryInterfac方法。m_pUnknownOuter究竟代表什么取决于创建组件时是否传递了外部组件的IUnknown指针。
5) B组件的类厂的CreateInstance方法内部创建CB类时,将IUnknown* pUnkownOuter参数传递给构造函数。创建成功后,调用B组件自身的(而不是外部的)NondelegationQueryInterface方法,将自身的INondelegationUnknown传递出去给组件A。
 
 
外部组件的实现
       外部组件A要创建组件B的实例,并保存B组件的INondelegationUnknown接口指针。该接口指针是通过上一节5)由B的类厂返回的。m_pUnknownInner变量保存了B接口的INondelegationUnknown接口指针。外部组件调用CoCreateInstance函数创建聚合组件时,iid必须等于IID_IUnknown。
       外部组件要修改自己的QueryInterface,当客户程序请求IB接口的时候,将调用m_pUnknownInner->QueryInterface方法。
       外部组件可以通过QueryInterface的代码来控制是否聚合B组件所有的接口。如果不想聚合B组件实现的IC接口。可以在输入参数iid等于IID_IC的时候,返回E_NOINTERFACE。
       聚合B组件的所有接口的方式称为盲聚合。盲聚合的缺点是:如果B组件实现了IPersist接口,客户调用IPersist::GetClassID方法时,获得的是CLSID_CB,这就暴露了内部的B组件;还有就是B组件实现的接口不了解A组件的状态,如果B组件实现了ISave接口,客户调用了它,却不能正确完成保存组件状态的功能。所以,一般我们要避免使用盲聚合,而要有选择的聚合。
       外部组件还有一个重要的任务就是控制内部组件的生命周期。因为内部组件拥有外部组件的IUnknown指针,这时候当调用内部组件的AddRef/Release,改变的是外部组件的引用计数。所以如果需要减少引用计数,应该调用pUnknowOuter(pUnknowOuter其实就是this指针的强制转换)->Release( )。CA类析构时,要采用以下特殊的做法保证不会被过早的析构和多次释放。
       m_cRef=1;
       IUnknown* pUnknownOuter = this;
       pUnknownOuter->AddRef( );
       m_pIB->Release( );
需要注意的是CA的析构函数是在A组件自身的引用计数为0时才会被调用,如果没有m_cRef=1这行代码,m_pIB->Release( )会导致析构函数再次被调用。
`      最后,析沟函数将释放组件B。通过调用B的INondelegationUnknown::Release( )方法。
代码如下:

      if(m_pUnknownInner!=NULL)

{
       m_pUnknownInner->Release ( );
}
 
以上总结主要来自于<<COM技术内幕>>。但是在ATL中,一切都被隐藏的很多。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/sheismylife/archive/2006/09/01/1154730.aspx