四.COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(iid, x, punk) 参ATL例程COMMAP 这个宏与上一节所讲的COM_INTERFACE_ENTRY_TEAR_OFF宏最主要的不同就在于,当查询分割对象中其他接口时,不会再新建新的对象。下面还是先看看它的典型用法:
class CTearOff2: public IDispatchImpl, public CComTearOffObjectBase { public: CTearOff2(){} ~CTearOff2(){} BEGIN_COM_MAP(CTearOff2) COM_INTERFACE_ENTRY(ITearOff2) END_COM_MAP() HRESULT STDMETHODCALLTYPE get_Name(BSTR* pbstrName) { *pbstrName = ::SysAllocString(L"ITearOff2"); return S_OK; } }; class COuter : public .... { public: BEGIN_COM_MAP(COuter) COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(IID_ITearOff2, CTearOff2, m_pUnkTearOff2.p) ...... END_COM_MAP() CComPtr m_pUnkTearOff2; ..... }; |
CTearOff2实现了分割接口ITearOff2,它的类定义与上一节所看见的CTearOff1一模一样可见不管是哪种分割接口,实现都是一样的,不同的地方在于COuter。在COuter中增加了一个成员变量m_pUnkTearOff2作为宏的一个参数。
我们继续用老办法跟踪它的内部执行过程,假设pOuter是已经获得的组件COuter有接口IOuter指针。
执行pOuter->QueryInterface(IID_ITearOff2, (void **)&pTear1);
函数堆栈一:
9.CTearOff2::_InternalQueryInterface(...) 8.ATL::CComCachedTearOffObject::QueryInterface(...)(第二次调用) 9.ATL::CComCachedTearOffObject::QueryInterface(...) 8.ATL::CComCreator>::CreateInstance() 7.ATL::CComObjectRootBase::_Cache(...) 6.COuter::_Cache(...) 5.ATL::AtlInternalQueryInterface(...) 4.ATL::CComObjectRootBase::InternalQueryInterface(...) 3,COuter::_InternalQueryInterface(...) 2.ATL::CComObject::QueryInterface(...) 1.CTestDlg::OnButton1() line 187 + 22 bytes |
解释:
1:pOuter->QueryInterface(IID_ITearOff2, (void **)&pTear1);
2-5:这段代码见到很多次了,不用再讲了,现在程序执行到
HRESULT hRes = pEntries->pFunc(pThis,
iid, ppvObject, pEntries->dw);
看来我们得看看这个宏的定义才能知道pFunc是执行的什么功能了。
#define COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(iid, x, punk)\ {&iid,\ (DWORD)&_CComCacheData<\ CComCreator< CComCachedTearOffObject< x> >,\ (DWORD)offsetof(_ComMapClass, punk)\ >::data,\ _Cache}, |
与我们上一节见的宏的定义不太一样,还是先跟踪下去再说。
6:原来在BEGIN_COM_MAP中也定义了_Cache函数:
static HRESULT WINAPI _Cache(void* pv,REFIID iid,void** ppvObject,DWORD dw )\ {\ ...... HRESULT hRes = CComObjectRootBase::_Cache(pv, iid, ppvObject, dw);\ ...... }\ |
7:看看CComObjectRootBase::_Cache的源码:
static HRESULT WINAPI _Cache(void* pv,REFIID iid,void** ppvObject,DWORD dw) { HRESULT hRes = E_NOINTERFACE; _ATL_CACHEDATA* pcd = (_ATL_CACHEDATA*)dw; IUnknown** pp = (IUnknown**)((DWORD)pv + pcd->dwOffsetVar); if (*pp == NULL) hRes = pcd->pFunc(pv, IID_IUnknown, (void**)pp); if (*pp != NULL) hRes = (*pp)->QueryInterface(iid, ppvObject); return hRes; } |
现在问题的关键是dw了,dw是从pEntries->dw传过来的。我们得看一下宏定义中的:
(DWORD)&_CComCacheData<\ CComCreator< CComCachedTearOffObject< x> >,\ (DWORD)offsetof(_ComMapClass, punk)\ >::data,\ |
是什么意思。
template _ATL_CACHEDATA _CComCacheData::data ={dwVar, Creator::CreateInstance}; |
CComCreator我们在前面已经见过它的定义了,它只有一个成员函数CreateInstance.
template class CComCachedTearOffObject : public IUnknown, public CComObjectRootEx { public: typedef contained _BaseClass; CComCachedTearOffObject(void* pv) : m_contained(((contained::_OwnerClass*)pv)->GetControllingUnknown()) { m_contained.m_pOwner = reinterpret_cast*>(pv); } CComContainedObject m_contained; }; |
CComCachedTearOffObject是这个宏与上一节所讲宏不同的关键所在,因为它包含了一个CComContainedObject的对象。这个对象的作用在查询的时候再讲。
我们再来看看offsetof的定义:
#define offsetof(s,m) (size_t)&(((s *)0)->m)
对(DWORD)offsetof(_ComMapClass, punk)来说,就是punk在_ComMapClass类中的偏移值。
现在来看看_ATL_CACHEDDATA是什么东西。
struct _ATL_CACHEDATA { DWORD dwOffsetVar; _ATL_CREATORFUNC* pFunc; }; typedef HRESULT (WINAPI _ATL_CREATORFUNC)(void* pv, REFIID riid, LPVOID* ppv); |
要注意的是从_Cached()函数传进来的参数dw就是_ATL_CACHEDATA结构的变量,所以可知道(DWORD)pv + pcd->dwOffsetVar)得到的就是在类COuter中定义的m_pUnkTearOff2的偏移中,所以IUnknown** pp就是指向m_pUnkTearOff2的一个指向指针的指针。而pdc->pFunc()则会调用CComCreator>::CreateInstance
8:下面将调用CComCreator::CreateInstance,将创建一个CComCachedTearOffObject<> 的对象实例。其构造函数定义如下:
CComCachedTearOffObject(void* pv) : m_contained(((contained::_OwnerClass*)pv)->GetControllingUnknown()) { ATLASSERT(m_contained.m_pOwner == NULL); m_contained.m_pOwner = reinterpret_cast*>(pv); } |
这里contained就是CTearOff2,contained::_OwnerClass就是COuter,可见m_contained 保存了外部对象的指针。
9:创建完对象后,将查询接口ITearOff2
STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) { //如果是IUnknown,...,返回IUnknwon接口指针 else hRes = m_contained._InternalQueryInterface(iid, ppvObject); ..... } } |
注意,这里把查询工作交给了m_contained,也就是一个CComContainedObject对象。不过现在查询的是IUnknown指针,别忘了,我们在COuter中还定义了一个IUnknown指针呢,现在查询的就是它!!
8:经过一系列退栈,退到_Cache()中,现在还要继续查询ITearOff2接口。是根据我们刚刚查询到的IUnknown指针查询ITearOff2。所以再一次进入ATL::CComCachedTearOffObject::QueryInterface(...),不过这回将调用的是m_contained._InternalQueryInterface(...)了。
9:因为CComContainedObject m_contained的基类是CTearOff2,所以将调用 CTearOff2::_InternalQueryInterface(...) 剩下的操作就没什么特别之处了,仅仅一般的查询操作。
执行pTear1->QueryInterface(ITearOff2, (void **)&pTear2);
函数堆栈二:
12.ATL::AtlInternalQueryInterface(...)
11.ATL::CComObjectRootBase::InternalQueryInterface(...)
10.CTearOff2::_InternalQueryInterface(...)
9.ATL::CComCachedTearOffObject::QueryInterface(...)
8.ATL::CComObjectRootBase::_Cache(...)
7.COuter::_Cache(...)
6.ATL::AtlInternalQueryInterface(...)
5.ATL::CComObjectRootBase::InternalQueryInterface(...)
4.COuter::_InternalQueryInterface(...)
3.ATL::CComObject::QueryInterface(...)
2.ATL::CComObjectRootBase::OuterQueryInterface(...)
1.ATL::CComContainedObject::QueryInterface(...)
解释:
1:第一步就可能使我们迷惑了,为什么执行的是CComContainedObject::QueryInterface在上一节中,执行的是ATL::CComTearOffObject::QueryInterface(...) ,所以我们也自然而然的猜想,这里应该执行的是CComCachedTearOffObject的函数。但是来看看CComCachedTearOffObject的定义:
template class CComCachedTearOffObject : public IUnknown, public CComObjectRootEx { ... }; |
原来CComCachedTearOffObject没有从contained类(在这里就是CTearOff2)中继承,而 CComTearOffObject却是从CTearOff1继承的!所以我们刚才得到的pTear1就不可能是 CComCachedTearOffObject的对象。而实际上,CComContainedObject是从CTearOff2继承的,在上面的函数堆栈中第9步查询ITearOff2接口时,把工作交给了m_contained, 这是个CComContainedObject对象,所以实际上最后查询得到的ITearOff2指向的是CComContainedObject对象。所以现在执行的会是
CComContainedObject::QueryInterface(...)!!! STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) { HRESULT hr = OuterQueryInterface(iid, ppvObject); if (FAILED(hr) && _GetRawUnknown() != m_pOuterUnknown) hr = _InternalQueryInterface(iid, ppvObject); return hr; } // m_pOuterUnknown |
2:HRESULT OuterQueryInterface(REFIID iid, void ** ppvObject)
{
return m_pOuterUnknown->QueryInterface(iid, ppvObject);
}
把查询工作交给外部对象完成,也就是COuter。第一、二步的功能与上一节中所讲的一样,都是交给外部对象去处理,不同之处在下面3-8:COuter中的查询过程与上例中无异,我们可以直接跳到第8步中
static HRESULT WINAPI _Cache(void* pv, REFIID iid, void** ppvObject,DWORD dw) { .... if (*pp == NULL) hRes = pcd->pFunc(pv, IID_IUnknown, (void**)pp); if (*pp != NULL) hRes = (*pp)->QueryInterface(iid, ppvObject); return hRes; } |
还记得我们在COuter中定义了一个IUnknown指针m_pUnkTearOff2吧,我们在第一次查询ITearOff2接口时,创建了CTearOff2对象,并查询一个IUnknown指针给了它.现在它就发挥作用了,在_Cache中将判断如果m_pUnkTearOff2不等于空,则表明CTearOff2已经创建就不会再创建它了,而是直接用它去查询接口。
9:所以现在将调用CComCachedTearOffObject::QueryInterface(...),在上一个函数堆栈中的第9步中我们已经看到了这个QueryInterface(...)的代码,它把查询工作交给m_contained._InternalQueryInterface(.),其实因为CComContainedObject中没有定义BEGIN_COM_MAP宏,所以也没有定义_InternalQueryInterface(),所以实际上调用的是它包含的类的函数,即CTearOff2::_InternalQueryInterface(...) 。
10-12:以下的工作就很简单了,不再赘述。