ATL中的聚合

忙了一整天,居然没有搞定ATL的实际的对象创建,问题的关键在于对于 com组件的聚合调用没有搞明白,如果没有这个
东西,ATL的CComObjectRootEx就可以了,只有简单的将IUnknown的实现简单的调一些inter的函数就可以了。但是为了
聚合ATL搞了一些CComObject的类出来了,这个打破常规的做法究竟为了什么,这些一些都要从聚合的思想看起。

聚合简单的说就是一种COM对象的重用技术。
它主要是为了编程简单而产生(但理解起来却不是那么容易)
外面的组件直接将请求转交给里面的组件,也就是说里面的组件直接把接口暴露给客户端,这个是它和包容最大的区别。

但是这个明显违背了QueryInterface的规则。

外面组件的QI和里面的QI所对应的IUnknown接口明显就不是一个接口。


聚合美妙的地方就在于她内部接口对于IUnkown接口的实现上,内部的IUnknown就可是是不会被暴露给
客户端的,因而客户端仅仅只能看到外部的IUnknown接口。

为了将请求转移给外部的IUnknown,内部的组件需要外部组件的IUnknown接口指针,外部组件在创建内部组件的时候将她的IUnknown接口传入,外部组件在调用CoCreateInstance 的时候将这个接口在第2个参数中传入
如果这个参数不为空,那么这个组件就被聚合了,否则就没有被聚合。

内部组件需要实现两个IUnknown接口来支持聚合,由内部组件自己控制生命周期的接口叫做 NonDelegating
IUnknown ,那个将请求自己导向外部接口的叫 Delegating IUnknown。

NonDelegating IUnknown接口是绝对不会暴露给客户端的,只有外部组件拥有一个这个接口的指针,外部
组件控制内部组件的生命周期通过这个接口。

当客户端通过内部组件的一个接口指针来调用QI的时候获得的是外部组件的IUnknown指针。
NonDelegateAddRef 和 NonDelegateRelease将负责内部组件的引用计数

NonDelegateQueryInterface的实现需要修改,以便当外部组件需要内部组件的IUnknown接口的时候,内部组件传给它的是NonDelegate IUnknown指针而不是IUnknown指针,

外部组件只能在创建内部组件的时候获取 NonDelegate IUnknown,因而NonDelegateQueryInterface将会在
Createinstance中调用。


HRESULT __stdcall CBasic::NonDelegateQueryInterface (const IID& iid,
    void **ppv)
  {
      if (iid == IID_IUnknown) {

  // When the AddRef will be called on the ppv before returning
  // from this function,the reference count of the inner component
  // is incremented by 1.

             *ppv = static_cast < INonDelegateUnknown*>(this);
      }
      else if (iid = IID_IAddSub) {

  // When the AddRef will be called on the ppv before
  // returning from this function,the reference count of the outer component
  // is incremented by 1. This is because that the IUnknown
  // implementation of IAddSub interface on inner component should
  // delegate to the outer component's controlling IUnknown implementation.
             *ppv = static_cast < IAddSub*<(this);
      }
      else {
             *ppv = NULL;
             return E_NOINTERFACE;
      }
    reinterpret_cast < IUnknown*<(*ppv)->AddRef();
    return S_OK;
  }

当外部组件请求属于内部组件的接口的时候,包括INonDelegateUnknown接口的时候,外部的引用计数将加1,
内部组件的引用计数也要加1.

内部组件的IClassFactory需要修改,将INonDelegateUnknown指针传给外部组件。
外部逐渐只有在内部组件创建的时候获取这个接口,因为在创建之后,所有的QI将都由外部的Unknown来
代理。


这里我们在来总结一下子,

对于QI

内部组件的QI将直接调用m_out(一个外部对象传入的IUnknown的接口指针),
那么它的QI就直接调用m_out->QI就可以了。
那么它的Addref和release 也可以照这个来处理,这个就是可以保证上面所说的,只有一个IUnknown。

下面来看看神奇的ATL是怎么干的

template <class contained>
class CComAggObject :
 public IUnknown,
 public CComObjectRootEx< typename contained::_ThreadModel::ThreadModelNoCS >
{
public:
 typedef contained _BaseClass;
 CComAggObject(void* pv) : m_contained(pv)
 {
  _pAtlModule->Lock();
 }
...

 STDMETHOD_(ULONG, AddRef)() {return InternalAddRef();}
 STDMETHOD_(ULONG, Release)()
 {
  ULONG l = InternalRelease();
  if (l == 0)
   delete this;
  return l;
 }
 STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
 {
  ATLASSERT(ppvObject != NULL);
  if (ppvObject == NULL)
   return E_POINTER;
  *ppvObject = NULL;

  HRESULT hRes = S_OK;
  if (InlineIsEqualUnknown(iid))
  {
   *ppvObject = (void*)(IUnknown*)this;
   AddRef();
#ifdef _ATL_DEBUG_INTERFACES
   _AtlDebugInterfacesModule.AddThunk((IUnknown**)ppvObject, (LPCTSTR)contained::_GetEntries()[-1].dw, iid);
#endif // _ATL_DEBUG_INTERFACES
  }
  else
   hRes = m_contained._InternalQueryInterface(iid, ppvObject);
  return hRes;
 }
...

CComContainedObject<contained> m_contained;
...
}
这个是个聚合时候调用的类,明显可以看到她的QI是怎么写的,当调用接口是IUnknown的是,调用的是内部类本身的IUnkown接口,但是当调用了别的接口,那么就会转到m_contained实例的接口上面了,然后所有的QI将都与这个实例没有任何关系了,生命周期的控制完全有外部内接管了。它只有机会addref 一次.

下面看看这个m_contained,这个成员变量是在实例创建的时候自然创建,它是我们真正要实现的类的封装,它的实现如下

template <class Base> //Base must be derived from CComObjectRoot
class CComContainedObject : public Base
{
public:
 typedef Base _BaseClass;
 CComContainedObject(void* pv) {m_pOuterUnknown = (IUnknown*)pv;}
#ifdef _ATL_DEBUG_INTERFACES
 virtual ~CComContainedObject()
 {
  _AtlDebugInterfacesModule.DeleteNonAddRefThunk(_GetRawUnknown());
  _AtlDebugInterfacesModule.DeleteNonAddRefThunk(m_pOuterUnknown);
 }
#endif

 STDMETHOD_(ULONG, AddRef)() throw() {return OuterAddRef();}
 STDMETHOD_(ULONG, Release)() throw() {return OuterRelease();}
 STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) throw()
 {
  return OuterQueryInterface(iid, ppvObject);
 }
 template <class Q>
 HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)
 {
  return QueryInterface(__uuidof(Q), (void**)pp);
 }
 //GetControllingUnknown may be virtual if the Base class has declared
 //DECLARE_GET_CONTROLLING_UNKNOWN()
 IUnknown* GetControllingUnknown() throw()
 {
#ifdef _ATL_DEBUG_INTERFACES
  IUnknown* p;
  _AtlDebugInterfacesModule.AddNonAddRefThunk(m_pOuterUnknown, _T("CComContainedObject"), &p);
  return p;
#else
  return m_pOuterUnknown;
#endif
 }
};

明显可以看见它所有IUnkown相关的函数都是调用的外部组件的函数,也就是说,他以后的QI调用的就是外部组件的QI,它的IUnknown接口等于就是
外部的IUnknown接口。而且它的引用计数从此就直接是外部组件的引用计数。

不过想想它的实现确实很巧妙,不得不佩服。它实现的所有东西和我们真正要实现的com类的接口没有任何的关系,我们可以放心的实现我们的com的逻辑,
而把这些复杂的重复的关系到架构设计的东西放心的交给ATL

那么在类厂的CreateInstance中就这么写
 if(pUnkOuter)
{
CComAggObject<CPenguin> * pobj=new CComAggObject<CPenguin>(pUnkOuter);
}
else
{
CComObject<CPenguin> * pobj=new CComObject<CPenguin>;
}

简单而且可读,优美的代码。

下面顺便说说CComObject,它就是一个IUnknown的实现类,当什么都让CComobjectRootEx干了,它所要做的就是将真实的IUnknown的实现使用Internal函数系列来代替,刚才为什么不直接就这样干,就是为了上面这么简单的几行代码,为了优雅选择了策略模式,不过是使用模板来实现的。很牛

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值