玩技术,Geeker
一个原创技术文章分享网站

COM编程——聚合

前言

聚合属于组件复用中比较重要的一种技术,理解起来也是有一定难度的;之前总结的包容是在外部组件的内部组合了内部组件,也就是说外部组件可能是内部组件的一层wrapper;这样有个问题就是,我们进行调用外部接口时,都是将对应的调用转发给内部组件;如果一个接口定义了N多操作,这种转发可能就是一种很费力的工作了。而聚合这种技术,正好能解决这种问题,我们可以直接通过外部组件去查询内部组件的接口。现在我们来仔细的总结一下聚合这种技术。

聚合简介

在总结类厂时,说到了CoCreateInstance函数,该函数的第二个参数是一个指向外部接口的IUnknown指针,当初也说了,这个指针主要用来聚合的。现在我们从最简单的想法来说说聚合的实现。

为什么这样不行?

比如现在有如下的组件,实现了IX接口,并通过聚合提供IY接口的一个外部组件的声明。

class CA : public IX
{
public:
     CA(void);
     ~CA(void);

     // IUnknown
     virtual HRESULT __stdcall QueryInterface(const IID &iid, void **ppv);
     virtual ULONG __stdcall AddRef();
     virtual ULONG __stdcall Release();

     // Interface IX
     virtual void __stdcall Fx() { std::cout<<"FX"<<std::endl; }

     // Constructor
     CA();

     // Destructor
     ~CA();

     // Initialization function called by the class factory to create contained component
     HRESULT Init();

private:
     // Reference count
     unsigned long m_iRef;

     // Pointer to inner component's IUnknown
     IUnknown *m_pInnerUnknown;
};

我们在实现这个CA类的QueryInterface时可以像下面这样去实现:

HRESULT __stdcall CA::QueryInterface(const IID &iid, void **ppv)
{
     std::cout<<"CA::QueryInterface"<<std::endl;

     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;
     }
     static_cast<IUnknown *>(*ppv)->AddRef();
     return S_OK;
}

这样就实现了外部组件调用内部组件的QueryInterface,从而在客户端获得在没有实现IY接口的类的QueryInterface中获得IY接口。这样很简单,但是非常不幸的是,它无法正常工作。为什么呢?大家还记不记得在之前总结的QueryInterface函数(2);实现QueryInterface需要遵从五条规则,其中的第五条是:如果能够从某接口获取某特定接口,则从任意接口都将能获取此接口。很明显,我们通过IX接口的QueryInterface查询获得了IY接口;但是反过来,我们再从IY接口能获得IX接口吗?很明显,这是无法完成的;这主要是因为外部组件和内部组件的IUknown接口的实现是不同的。这样的话,客户实际得到的是两个不同IUnknown接口,即内部组件和外部组件的;这就给客户带来了一些混乱,因为每一个IUnknown均将实现一个QueryInterface,而每一个QueryInterface均将分别支持一个不同的接口集。但客户应完全独立于聚合组件的实现,它不应该知道外部组件聚合了某个内部组件,并且永远不应看到内部组件的IUnknown;这才是聚合的真正含义。

聚合的实现原理

之前也说了CoCreateInstance函数,该函数的第二个参数是一个指向外部接口的IUnknown指针;如果第二个参数不为空,那么此时表示我们想进行组件的组合。使用传给类厂接口的成员函数CreateInstance的IUnknown接口指针,被创建的组件将知道它是被聚合的。

为了支持聚合,内部组件实际上将实现两个IUnknown接口。在没有聚合的情况下,我调用实现IY接口的组件时,就调用最普通的QueryInterface;当有聚合的情况时,我调用实现IY的接口的组件时,就将对QueryInterface的调用转发给外部组件的IUnknown。这样一来,就能满足实现QueryInterface需要遵从的第五条规则。

聚合的实现

聚合的原理清楚了以后,聚合的实现也就一目了然了;聚合可以看作是包容的一个特例,在外部组件里面也包含一个内部组件的实例。关于具体的实现,请参考《COM技术内幕》。如果有什么好的心得体会,希望能和我进行交流。

2014年2月13日 于大连,东软。

打赏

未经允许不得转载:果冻想 » COM编程——聚合

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

在这里玩技术,享受技术带来的疯狂

捐赠名单关于果冻