总结:
COM_INTERFACE_ENTRY_CACHED_TEAR_OFF是个相对比较麻烦的宏,它与上一节介绍的宏相比不同之处就在于创建分割接口对象的过程只用进行一次,如果对象已经创建,则下一次查询该对象的接口时不会再创建一个新的分割对象。为了达到这个目的,它在外部对象中包含了一个IUnknown指针,并在第一次创建分割对象时查询这个IUnknown指针, 这样就可以通过判断这个指针是否为空来知道这个分割对象是否已经创建,从而决定是否创建新的分割对象,并通过它去查询分割对象内其它接口。这里特别需要注意的是,实际上有两个对象被创建,一个是CComCachedTearOffObject,另一个是CComContainedObject。并且第一个对象内部实现了第二个对象,真正的查询工作也是交给第二个对象去做。COuter::m_pUnkTearOff2是前面一个对象的IUnknown指针,当用它去查询ITearOff2时,实际上是交给了其内部对象m_contained去做了,这在第8、9步可以看得很清楚。
终于把这个宏讲完了,我感觉这个宏可能是ATL接口映射宏中实现最复杂的了,其实它并没有利用到CComContainedObject的真正功能。感觉实现这个宏也许不应这么麻烦的。 五.COM_INTERFACE_ENTRY_AGGREGATE(iid, punk) 参ATL例程COMMAP
这一节中将介绍ATL中用于聚集对象的宏。聚集对象的概念请参阅其它参考书。现在先看一看这个宏的典型用法:
class CAgg : public IDispatchImpl, public ISupportErrorInfo, public CComObjectRoot, public CComCoClass { ..... }; |
CAgg是一个聚集类,它的实现与一般的ATL组件没有区别,只是注意在它的类定义中不要加入DECLARE_NO_AGGREGATABLE.
class COuter : public CChainBase, public IDispatchImpl, public CComCoClass { HRESULT FinalConstruct(); void FinalRelease(); BEGIN_COM_MAP(COuter) COM_INTERFACE_ENTRY_AGGREGATE(IID_IAgg, m_pUnkAgg.p) END_COM_MAP() DECLARE_GET_CONTROLLING_UNKNOWN() CComPtr m_pUnkAgg; }; |
COuter包含了聚合组件CAgg,它包含了几个不同之处:
(1)加入了COM_INTERFACE_ENTRY_AGGREGATE(IID_IAgg, m_pUnkAgg.p)宏。
#define COM_INTERFACE_ENTRY_AGGREGATE(iid, punk)\ {&iid,\ (DWORD)offsetof(_ComMapClass, punk),\ _Delegate}, |
offsetof我们在上一节中已经见过,可以猜到它求的就是punk在类中的位置。也就是m_pUnkAgg在COuter中的位置。
(2)加入了宏DECLARE_GET_CONTROLLING_UNKNOWN(),其定义为:
#define DECLARE_GET_CONTROLLING_UNKNOWN() public:\
virtual IUnknown* GetControllingUnknown() {return GetUnknown();}
我们也必要继续深究下去,仅从字面意思就可以看出这个函数将返回组件的IUnknown
指针。
(3)在COuter中加入一个成员变量:CComPtr m_pUnkAgg; m_pUnkAgg将用于获得被聚集组件的IUnknown指针。
(4)重载了FinalConstruct,FinalRelease
HRESULT COuter::FinalConstruct() { IUnknown* pUnkOuter = GetControllingUnknown(); HRESULT hRes = CoCreateInstance(CLSID_CAgg, pUnkOuter, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&m_pUnkAgg); if (hRes != S_OK) return hRes; return S_OK; } void COuter::FinalRelease() { m_pUnkAgg.Release(); ..... } |
当创建组件COuter后将会调用FinalConstruct,所以会在这里创建聚集组件。原则上聚集组件可以仅在需要的时候才创建,但也可以随着包含它的组件一起创建。聚集组件的创建没什么特别之处,只是要注意它将查询IUnknown指针,并返回给m_pUnkAgg.外部组件将通过m_pUnkAgg操作聚集组件。另外注意到使用pUnkOuter作为CoCreateInstance 的参数,这将导致创建CComAggObject对象,内部包含一个ComContainedObject的包含对象。向上一节中的CComCachedTearOff<>类似,CComAggObject也不是从COuter派生的,所以真正的组件对象不是CComAggObject对象,而是它内部包 含的CComContainedObject对象。同样pUnkOuter得到的将是CComAggObject<>的IUnknown指针,也同样调用它的QueryInterface会转而调用CComContainedObject的_InternalQueryInterface函数,运行pOuter->QueryInterface(IID_IAgg, (void **)&pAgg1)
函数堆栈一:
9.ATL::AtlInternalQueryInterface(...)
8.ATL::CComObjectRootBase::InternalQueryInterface(...)
7.CAgg::_InternalQueryInterface(...)
6.ATL::CComAggObject::QueryInterface(...)
5.ATL::CComObjectRootBase::_Delegate(...)
4.ATL::AtlInternalQueryInterface(...)
3.ATL::CComObjectRootBase::InternalQueryInterface(...)
2.COuter::_InternalQueryInterface(...)
1.ATL::CComObject::QueryInterface(...)
解释:
1-5:这几步函数调用我们已经见了很多次了,因为在这个宏定义使用了_Delegate,所以将调用CComObjectRootBase::_Delegate(...).
static HRESULT _Delegate(void* pv,REFIID iid,void** ppvObject,DWORD dw) { HRESULT hRes = E_NOINTERFACE; IUnknown* p = *(IUnknown**)((DWORD)pv + dw); if (p != NULL) hRes = p->QueryInterface(iid, ppvObject); return hRes; } |
第二句话的含义我们在上一节中已经见过了,最后的结果p=COuter::m_pUnkAgg.
6:正如我们刚才所料,现在调用的是CComAggObject::QueryInterface()
STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) { //如果查询的是IUnknown,则.... else hRes = m_contained._InternalQueryInterface(iid, ppvObject); return hRes; } |
也正如我们所料,将交给它的包含对象去做。
7-9:同上一节一样,将交给CAgg::_InternalQueryInterface(...),剩下的工作将由CAgg完成了。最后返回的指针实际上将是CComContainedObject组件的接口指针。
运行pAgg1->QueryInterface(IID_IAgg, (void **)&pAgg2)
函数堆栈二:
9.CAgg::_InternalQueryInterface(...)
8.ATL::CComAggObject::QueryInterface(...)
7.ATL::CComObjectRootBase::_Delegate(...)
6.ATL::AtlInternalQueryInterface(...)
5.ATL::CComObjectRootBase::InternalQueryInterface(...)
4.COuter::_InternalQueryInterface(...)
3.ATL::CComObject::QueryInterface(...)
2.ATL::CComObjectRootBase::OuterQueryInterface(...)
1.ATL::CComContainedObject::QueryInterface(...)
解释:
1-9:浏览整个堆栈,与我们上一节所见的堆栈二太相近了,这是因为都是使用了包含对象。包含对象起了个代理的作用,他先把查询交给外部对象(COuter)去做(第1,2步), 当外部对象发现要查询的是聚集组件的接口时(IAgg),就会再把查询交还给它保留的聚集组件的指针(m_pUnkAgg,第7步中,注意这不是真正的聚集组件),m_pUnkAgg再把查询交给包含对象(第8步中),包含对象再把查询交给真正实现接口的类CAgg(第9步)。
若外部对象发现要查询的是外部组件的接口时,那就很简单了,直接查询就行了。这样就防止了外部组件与聚集组件查询操作的不一致性。