com聚合

来源:互联网 发布:普通话读音软件 编辑:程序博客网 时间:2024/04/29 00:22

COM聚合

标签: nullinterfaceobject服务器class任务
8962人阅读 评论(1)收藏举报
本文章已收录于:
分类:
作者同类文章X
     
    COM聚合...1
    聚合的概念...1
    内部组件的实现...2
    外部组件的实现...14
    ATL7对内部组件的支持...33
    分析:...54
    DECLARE_CLASSFACTORY()-------------55
    DECLARE_AGGREGATABLE(T)---------------------87
    ATL7对外部组件的支持...97
     
    聚合的概念
    聚合源自组件重用。当有两个组件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中,一切都被隐藏的很多。
    ATL7对内部组件的支持
            使用ATL7.1创建ATL简单对象(传统COM对象时)向导中可以选择是否支持聚合,默认是支持的。我们现在开始创建内部组件。
     
     
       
     
     
          
     
          
     
          
     
           按照上图步骤创建组件B,并且实现了IB接口。现在我们为IB增加方法F( )。F()方法很简单,只是弹出一个消息框。
           STDMETHODIMP CB::F(void)
    {
              MessageBox(NULL,"OK","OK",MB_OK);
              return S_OK;
    }
     
    分析:
    DECLARE_CLASSFACTORY()-------------
    ATL中,类厂是由CComCoClass类的宏DECLARE_CLASSFACTORY()实现的。该宏的定义为:#define DECLARE_CLASSFACTORY() DECLARE_CLASSFACTORY_EX(ATL::CComClassFactory)
    CComClassFactory实现了IClassFactory接口。最主要的就是实现了创建组件的函数。
    // IClassFactory
         STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter, REFIID riid,void** ppvObj)
         {
             ATLASSERT(m_pfnCreateInstance != NULL);
             HRESULT hRes = E_POINTER;
             if (ppvObj != NULL)
             {
                  *ppvObj = NULL;
                  // can't ask for anything other than IUnknown when aggregating
                  if ((pUnkOuter != NULL) && !InlineIsEqualUnknown(riid))
                  {
                       ATLTRACE(atlTraceCOM, 0, _T("CComClassFactory: asked for non IUnknown interface while creating an aggregated object"));
                       hRes = CLASS_E_NOAGGREGATION;
                  }
                  else
                       hRes = m_pfnCreateInstance(pUnkOuter, riid, ppvObj);
             }
             return hRes;
    }
    m_pfnCreateInstance是创建COM对象的函数的指针。创建CB类的函数指针是被保存到对象映射表中的用于创建CB类的函数指针。对象映射表是一个_ATL_OBJMAP_ENTRY结构数组,该结构数组还保存了创建类厂的函数指针。该结构数组的初始化是通过B.h中的宏OBJECT_ENTRY_AUTO(__uuidof(B), CB)来完成的。该宏取代了ATL3中的BEGIN_OBJECT_MAP()/OBJECT_ENTRY()/END_OBJECT_MAP()宏。
     
    DECLARE_CLASSFACTORY_EX定义为#define DECLARE_CLASSFACTORY_EX(cf)typedef ATL::CComCreator< ATL::CComObjectCached< cf > > _ClassFactoryCreatorClass;
    CComObjectCached类派生自CComClassFactory,该类的作用是使得组件类的生命周期和服务器生命周期相同,所以适用于进程内组件。CComCreator类只提供了一个CreateInstance函数,用来帮助我们创建一个组件类,这里就帮助我们创建类厂。
    所以宏DECLARE_CLASSFACTORY()的功能就是:
    1) 通过从CComClassFactory类派生实现了IClassFactory接口
    2) 使用CComObjectCached类指定了类厂对象的生命周期和服务器生命周期相同
    3) 定义了_ClassFactoryCreatorClass类型,该类型的CreateInstance函数可以创建类厂对象,而且是采用多步构造的方式,防止了构造期间的意外析构。
     
     
    DECLARE_AGGREGATABLE(T)---------------------
    该宏的定义如下:
    #define DECLARE_AGGREGATABLE(x)public:/
         typedef ATL::CComCreator2< ATL::CComCreator< ATL::CComObject< x > >, ATL::CComCreator< ATL::CComAggObject< x > > > _CreatorClass;
        _CreatorClass类型的CreateInstance函数即可以创建独立组件,也可以创建聚合组件,取决于传入的pOuterIUnknown参数是否为NULL。
    值得一提的是CComAggObject类通过两次从CComObjectRootEx继承,实现了内部组件的两个IUnknown接口,一个是IUnknown*,一个是INondelegationUnknown*(其实ATL中不使用INondelegationUnknown名字,但是作用一样和INondelegationUnknown一样)。
    如果我们在向导中选择不支持聚合,向导将会在我们的CB类内加上宏#define DECLARE_NOT_AGGREGATABLE(x)
    #define DECLARE_NOT_AGGREGATABLE(x)public:/
         typedef ATL::CComCreator2< ATL::CComCreator< ATL::CComObject< x > >, ATL::CComFailCreator<CLASS_E_NOAGGREGATION> > _CreatorClass;
    这样_CreatorClass类型的CreateInstance函数当接收到不为NULL的pOuterIUnknown参数时会失败。
     
    所以,当我们有了ATL的支持后,我们创建内部组件的一切工作只是在向导中选择支持聚合,轻松惬意!
    ATL7对外部组件的支持
           现在我们另创建一个名为OuterComponent的ATL项目,创建A组件并实现IA接口。创建步骤就不再叙述。A组件是否支持聚合无关紧要,我们这里就默认支持吧。在OuterComponent项目中导入InnerComponent.dll,然后将CComPtr<IUnknown> m_pIB作为CA的成员变量。请参考下面的代码的注释
    // A.h : CA 的声明
     
    #pragmaonce
    #include "resource.h"      // 主符号
     
    #include "OuterComponent.h"
     
     
    // CA
     
    class ATL_NO_VTABLE CA :
         public CComObjectRootEx<CComSingleThreadModel>,
         public CComCoClass<CA, &CLSID_A>,
         public IDispatchImpl<IA, &IID_IA, &LIBID_OuterComponentLib,/*wMajor =*/ 1, /*wMinor =*/ 0>
    {
    public:
         CA()
         {
         }
     
    DECLARE_REGISTRY_RESOURCEID(IDR_A)
     
    BEGIN_COM_MAP(CA)
         COM_INTERFACE_ENTRY(IA)
         COM_INTERFACE_ENTRY(IDispatch)
         COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IB,m_spunkIB.p,CLSID_B)//自动聚合IB接口,有了该宏就不需要在FinalConstruct函数中创建B对象了
    END_COM_MAP()
     
         DECLARE_GET_CONTROLLING_UNKNOWN()//创建虚函数IUnknown* GetControllingUnknown(),CComContainedObject类会继承并实现该函数,然后将返回最外层组件的IUnknown指针
         DECLARE_PROTECT_FINAL_CONSTRUCT()
         HRESULT FinalConstruct()
         {
             return S_OK;
         }
        
         void FinalRelease()
         {
             m_spunkIB.Release();//避免两次析构内部组件
         }
    private:
         CComPtr<IUnknown> m_spunkIB;//保存被聚合的内部组件的IUnknown接口
    };
    OBJECT_ENTRY_AUTO(__uuidof(A), CA)
     
       
        现在我们来用一下,创建一个客户程序,它导入A和B的组件类型库,然后创建A组件,然后查询IB接口并调用IB::F()方法。
     
    // Client.cpp : 定义控制台应用程序的入口点。
    //
     
    #include "stdafx.h"
    #import "../../OuterComponent/OuterComponent/debug/OuterComponent.tlb" no_namespace named_guids raw_interfaces_only
    #import "../../InnerComponent/InnerComponent/debug/InnerComponent.tlb" no_namespace named_guids raw_interfaces_only
     
     
    int _tmain(int argc, _TCHAR* argv[])
    {
         ::CoInitialize(NULL);
         CComPtr<IA> pA;
         HRESULT hr=pA.CoCreateInstance(CLSID_A);
         CComPtr<IB> pB;
         hr=pA->QueryInterface(&pB);
         pB->F();
         pB.Release();
         pA.Release();
         ::CoUninitialize();
         return 0;
    }
     
    一切成功。ATL大大简化了我们对于聚合的使用。
     
    陈抒
    2005-9-15
     
     
    0
    0
     
     

    我的同类文章

    http://blog.csdn.net
    • ATL智能指针类剖析2009-11-04
    • vc2005 release 版 系统文件安装包2006-09-05
    • ATL7.1编写ActiveX控件2006-09-01
    • COM持久存储2006-09-01
    • COM安全编程2006-09-01
    • Excel开发入门(C#和C++实例)2009-09-29
    • Programming Word Add-In Component2006-09-01
    • ATL7.1创建连接点组件2006-09-01
    • COM+编程研究之对象池、JITA2006-09-01
    • 高性能Web Service数据库编程2006-08-31
    更多文章
    0 0
    原创粉丝点击