com 聚合的实现

来源:互联网 发布:Windows 8.1 最高权限 编辑:程序博客网 时间:2024/05/17 02:59

COM接口聚合实现解惑(C++虚表)

标签: c++nullclass工作serverc
1829人阅读 评论(8)收藏举报
本文章已收录于:
分类:
作者同类文章X
    作者同类文章X

      最近看潘爱民的《COM原理与应用》,看到接口的聚合实现时,产生一个疑惑。COM的这个特性的背后隐藏着一个关于C++虚表的知识点。如果对C++的虚表没有一定的认识就会被绕进去,被搞得稀里糊涂。经过和朋友的一番探讨总算搞清楚。特整理成此文

      我们知道支持被聚合使用的CA接口要实现一组和IUnknown接口一致的非委托Unknown接口,这一组接口完成实际的QueryInterface, AddRef 和 Release的工作。而真正的IUnknow接口的工作就仅仅是判断该对象是否是被聚合使用(通过一个指向IUnknown的指针成员m_pUnknownOuter),如果该对象被聚合使用(m_pUnknownOuter不等于Null)则将调用转向m_pUnknownOuter所指的外部对象CB.

      CB在初始化时,聚合使用了CA。如下:

      HRESULT CB::Init()
      {
          IUnknown *pUnknownOuter = (IUnknown *)this;
          HRESULT result = ::CoCreateInstance(CLSID_CompA, pUnknownOuter,
                              CLSCTX_INPROC_SERVER,
                              IID_IUnknown, (void **)& m_pUnknownInner) ;

          if (FAILED(result))
                  return E_FAIL;
          
          result = m_pUnknownInner->QueryInterface(IID_SomeInterface, (void **)&m_pCAInterface);
              if (FAILED(result))
          {
              m_pUnknownInner->Release();
              return E_FAIL;
          }

          pUnknownOuter->Release();
          return S_OK;
      }

      1) CB调用CoCreateInstance聚合使用CA时,CB的m_pUnknownInner指向了CA对象,而CA对象的m_pUnknownOuter成员也指向了CB对象。

      2) CB调用m_pUnknownInner->QueryInterface(IID_CAInterface, (void **)&m_pCAInterface); 来请求CA的接口,CA的QueryInterface如下:

      HRESULT CA::QueryInterface(const IID& iid, void **ppv)
      {
          if  ( m_pUnknownOuter != NULL )
          {
              return m_pUnknownOuter->QueryInterface(iid, ppv);
          }
          else
          {
              return NondelegationQueryInterface(iid, ppv);
          }
      }

      由于CA对象的m_pUnknownOuter不等于Null, 调用将被转到CB (return m_pUnknownOuter->QueryInterface(iid, ppv);)
      那么来看看CB的QueryInterface:

      HRESULT CB::QueryInterface(const IID& iid, void **ppv)
      {
          if ( iid == IID_IUnknown )
          {
              *ppv = (IUnknown *) this ;
              ((IUnknown *)(*ppv))->AddRef() ;
          } else if ( iid == IID_OtherInterface )
          {
              *ppv = (IOtherInterface *) this ;
              ((IOtherInterface *)(*ppv))->AddRef() ;
          } else if ( iid == IID_SomeInterface)
          {
              return m_pUnknownInner->QueryInterface(iid, ppv) ;
          }
      else
          {
              *ppv = NULL;
              return E_NOINTERFACE ;
          }
          return S_OK;
      }

      CB又将调用转给了CA, CA又再转给CB,,, 死循环!!

      不过,实际上,例子程序运行正常,并没有产生死循环。这当中发生了一些我们不知道的事情。这就是C++的虚表在作怪!下来,我们来分析为什么程序运行时没有进入死循环。

      先给出CA的定义:
      class INondelegatingUnknown
      {
      public:
        virtual HRESULT  __stdcall  NondelegationQueryInterface(const IID& iid, void **ppv) = 0 ;
          virtual ULONG     __stdcall  NondelegatingAddRef() = 0;
          virtual ULONG     __stdcall  NondelegationRelease() = 0;
      };

      class CA : public ISomeInterface, public INondelegatingUnknown
      {
      protected:
           ULONG           m_Ref;

      public:
           CA(IUnknown *pUnknownOuter);
           ~CA();

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

          // Nondelegating IUnknown
          virtual HRESULT   __stdcall  NondelegationQueryInterface(const IID& iid, void **ppv);
          virtual ULONG      __stdcall  NondelegatingAddRef();
          virtual ULONG      __stdcall  NondelegationRelease();

          virtual HRESULT __stdcall SomeFunction( ) ;

          private :
              IUnknown  *m_pUnknownOuter;  // pointer to outer IUnknown
      };

      仔细读《COM原理与应用》第105页,第4行。“(2) CoCreateInstance函数返回之后,得到了m_pUnknownInner指针,指向对象A的非委托IUnknown。”
      在CB的Init中,虽然m_pUnknownInner是一个IUnknow*指针,但得到的是Nondelegating IUnknown接口,而不是IUnknown接口。
      所以在后面的第二步 result = m_pUnknownInner->QueryInterface(IID_SomeInterface, (void **)&m_pCAInterface); 时实际被调用的是NondelegationQueryInterface(const IID& iid, void **ppv);

      请仔细看CA的Nondelegating IUnknown接口实现:
      HRESULT CA::NondelegationQueryInterface(const IID& iid, void **ppv)
      {
          if ( iid == IID_IUnknown )
          {
             *ppv = (INondelegatingUnknown *) this ;
               ((IUnknown *)(*ppv))->AddRef() ;
          } else if ( iid == IID_SomeInterface )
          {
              *ppv = (ISomeInterface *) this ;
              ((ISomeInterface *)(*ppv))->AddRef() ;
          }
          else
          {
              *ppv = NULL;
              return E_NOINTERFACE ;
          }
          return S_OK;
      }

      那么这究竟是怎么做到的呢?!毕竟m_pUnknownInner是一个IUnknow*指针,而且在CB中,我们是这样调用的m_pUnknownInner->QueryInterface,用得是QueryInterface!而不是NondelegationQueryInterface!怎么会调用到NondelegationQueryInterface呢?
      在讲出为什么之前,请大家试用改一下例子程序,把NondelegationQueryInterface的次序小小地调整一下。如下:

      class INondelegatingUnknown
      {
      public:
        virtual HRESULT  __stdcall  NondelegationQueryInterface(const IID& iid, void **ppv) = 0 ;
          virtual ULONG     __stdcall  NondelegatingAddRef() = 0;
          virtual ULONG     __stdcall  NondelegationRelease() = 0;
      };

      变成:
      class INondelegatingUnknown
      {
      public:
          virtual ULONG     __stdcall  NondelegatingAddRef() = 0;
        virtual HRESULT  __stdcall  NondelegationQueryInterface(const IID& iid, void **ppv) = 0 ;
          virtual ULONG     __stdcall  NondelegationRelease() = 0;
      };

      然后再运行代码,相信你一定会运行时出错的。这是因为我们在CB中调用m_pUnknownInner->QueryInterface这一句时,并不仅仅是根据函数名QueryInterface来调用函数的,因为CA是一个有虚函数的对象,所以在CA对象中存在一个虚表,当调用函数时,首先是根据函数名来计算函数在虚表中的索引值,然后再用这个索引到虚表中去调用实现的函数。因为QueryInterface在IUnknown中是第一个被定义的,所以它在虚表中的索引是0,当调用m_pUnknownInner->QueryInterface时,会去调用虚表中的第一个函数,由于我们改变了INondelegatingUnknown定义的顺序,INondelegatingUnknown的虚表中的第一个函数是NondelegatingAddRef,它是没有参数的。所以当发生带着两个参数的调用时,产生了运行时的错误!

      讲到这里,问题好像清楚了,又好像有哪里没说明白。到底是哪个虚表?哪个对象的虚表?

      其实,在我们的讨论中只有两个对象,一个CB,一个CA其他的什么IUnknown,INondelegatingUnknown,ISomeInterface只不过是接口而已。很显然CB我们不用关心,主要是CA。

      Main

      基本上,CA的对象应该是这副德性:

      VT

      我们注意CA::NondelegationQueryInterface的实现中
      if ( iid == IID_IUnknown )
          {
             *ppv = (INondelegatingUnknown *) this ;
               ((IUnknown *)(*ppv))->AddRef() ;
          }
      这里对this做了一次强制转换,(INondelegatingUnknown *) this 表达式的结果就仅仅是INondelegatingUnknown虚表的指针。而*ppv需要的正好仅仅是这个虚表的指针。所以在在CB中调用m_pUnknownInner->QueryInterface,程序去找虚表中的第一项。所以实际被调用的就是CA::NondelegationQueryInterface。

      0
      0
       
       

      我的同类文章

      http://blog.csdn.net
      • Windows mobile 连接管理2009-03-31
        0 0
        原创粉丝点击