忙了一整天,居然没有搞定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函数系列来代替,刚才为什么不直接就这样干,就是为了上面这么简单的几行代码,为了优雅选择了策略模式,不过是使用模板来实现的。很牛