三、COM_INTERFACE_ENTRY_TEAR_OFF(iid, x)
使用这个宏的目的就是为了把一些很少用到的接口放在一个单独的组件中实现,仅当查询到这个接口时,才创建这个组件,并且当它的引用计数减为0时就会被释放掉。我们知道ATL中组件是通过多重继承实现的,每继承一个接口,在为它分配的内存块中就会多一个虚函数表指针,用这个宏就可以为每个组件的实例节省下这一个虚函数表指针来(一个指针4个字节,好象也不多啊,呵呵)
下面我们来看它的典型用法:
CTearOff1实现了Tear-off接口ITearOff1,实现方法与其他组件并无不同。唯一不同的是它从CComTearOffObjectBase继承,CComTearOffObjectBase定义如下:
我们又看到了我们熟悉的一个类CComObject,它是组件的真正生成类。从上面的定义中可知道CComTearOffObjectBase主要功能就是包含了一个指向外部对象(在这里就是我们的组件类CComObject)的指针。它的功能将在后面看到。
我们继续用我们的老办法来跟踪一下看看它的执行过程。假设pOuter是我们已经获得的组件的IOuter接口指针。
执行pOuter->QueryInterface(IID_ITearOff1, (void **)&pTear1);
函数堆栈一:
解释:
1--4:这些代码已经遇到过很多次了,我们还是集中精力看看核心代码:
当在COuter的接口映射数组中找到ITearOff1后,因为它不是一个简单接口,所以要执行pEntries->pFunc(....)。
我们先来看看COM_INTERFACE_ENTRY_TEAR_OFF的定义:
看不太明白,还是继续我们路由得了
5:原来_Creator是CComObjectRootBase的静态成员函数,它可是COuter的一个基类啊,所以才可以这样写而不会编译出错。看看它的实现吧:
源代码都列出来了,不用我多说,大家也都能看懂了。继续路由吧
6:绕了一大圈,现在我们调用的应该是
同我们所见到的大多数Creator类一样,它也只有一个静态CreateInstance函数。现在我们终于可以创建我们分割组件了,它不是CTearOff1,它也是经了一层包装的,是 CComTearOffObject! 现在我们再来看看它的构造函数干了些什么事:
还记得CTearOff1是从CComTearOffObjectBase继承的吗,这个基类包含了一个成员变量m_pOwner,现在它被赋值为指向它的外部对象的指针了。
7.现在终于把这个实现分割接口的组件创建了,剩下的在CTearOff1中查询ITearOff1的工作已经是重复劳动了,不再赘述。
执行pTear1->QueryInterface(ITearOff1, (void **)&pTear2)
一个实现分割接口的组件有可能包含多个分割接口,我们来检测一下它的查询过程。
函数堆栈二:
解释:
1:
还记得我们创建的分割组件是CComTearOffObject< CTearOff1 >吗?现在执行查询操作的是它的成员函数。它的实现很简单,事实上它什么也没做,仅仅是把它交给它的外部对象(即CComObject< COuter >)去做了。还记得m_pOwner是在构造函数里赋值的吧。现在是否感到有些不妙呢?呵呵
2、3:果然,现在已经不用再看下去了,剩下的将是重复我们在调用第一条查询操作所做的一切。这个过程很简单,但它也隐含说明了一点:若对一个实现分割接口的组件每查询一次它的接口,就会调用一个新的实例!!!在上例中,最后的结果pTear2和pTear1 是不一样的!!这显然是浪费!
在下一节中,我们将介绍一个可以解决这个问题的宏!
使用这个宏的目的就是为了把一些很少用到的接口放在一个单独的组件中实现,仅当查询到这个接口时,才创建这个组件,并且当它的引用计数减为0时就会被释放掉。我们知道ATL中组件是通过多重继承实现的,每继承一个接口,在为它分配的内存块中就会多一个虚函数表指针,用这个宏就可以为每个组件的实例节省下这一个虚函数表指针来(一个指针4个字节,好象也不多啊,呵呵)
下面我们来看它的典型用法:
class CTearOff1: //该类是专门用来实现分割接口ITearOff1的 public IDispatchImpl< ITearOff1, &IID_ITearOff1, &LIBID_COMMAPLib >, public CComTearOffObjectBase //外部对象 { public: CTearOff1(){} ~CTearOff1(){} BEGIN_COM_MAP(CTearOff1) COM_INTERFACE_ENTRY(ITearOff1) END_COM_MAP() HRESULT STDMETHODCALLTYPE get_Name(BSTR* pbstrName) { *pbstrName = ::SysAllocString(L"ITearOff1"); return S_OK; } }; class COuter : public ..... //我们真正要实现的组件 { public: ........... BEGIN_COM_MAP(COuter) ........... COM_INTERFACE_ENTRY_TEAR_OFF(IID_ITearOff1, CTearOff1) END_COM_MAP() ........... }; |
CTearOff1实现了Tear-off接口ITearOff1,实现方法与其他组件并无不同。唯一不同的是它从CComTearOffObjectBase继承,CComTearOffObjectBase定义如下:
template < class Owner, class ThreadModel = CComObjectThreadModel > class CComTearOffObjectBase : public CComObjectRootEx { public: typedef Owner _OwnerClass; CComObject* m_pOwner; CComTearOffObjectBase() {m_pOwner = NULL;} }; |
我们又看到了我们熟悉的一个类CComObject,它是组件的真正生成类。从上面的定义中可知道CComTearOffObjectBase主要功能就是包含了一个指向外部对象(在这里就是我们的组件类CComObject)的指针。它的功能将在后面看到。
我们继续用我们的老办法来跟踪一下看看它的执行过程。假设pOuter是我们已经获得的组件的IOuter接口指针。
执行pOuter->QueryInterface(IID_ITearOff1, (void **)&pTear1);
函数堆栈一:
7.CTearOff1::_InternalQueryInterface(...) 6.ATL::CComInternalCreator< ATL::CComTearOffObject< CTearOff1 > >::CreateInstance(...) 5.ATL::CComObjectRootBase::_Creator(...) 4.ATL::AtlInternalQueryInterface(...) 3.ATL::CComObjectRootBase::InternalQueryInterface(...) 2.COuter::_InternalQueryInterface(...) 1.ATL::CComObject< COuter >::QueryInterface(...) |
解释:
1--4:这些代码已经遇到过很多次了,我们还是集中精力看看核心代码:
ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis, const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject) { //.......... while (pEntries->pFunc != NULL) { BOOL bBlind = (pEntries->piid == NULL); if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid)) { if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) //offset { //若是简单接口,.... } else //actual function call { HRESULT hRes = pEntries->pFunc(pThis, iid, ppvObject, pEntries->dw); if (hRes == S_OK || (!bBlind && FAILED(hRes))) return hRes; } } pEntries++; } return E_NOINTERFACE; } |
当在COuter的接口映射数组中找到ITearOff1后,因为它不是一个简单接口,所以要执行pEntries->pFunc(....)。
我们先来看看COM_INTERFACE_ENTRY_TEAR_OFF的定义:
#define COM_INTERFACE_ENTRY_TEAR_OFF(iid, x)/ {&iid,/ (DWORD)&_CComCreatorData</ CComInternalCreator< CComTearOffObject< x > >/ >::data,/ _Creator}, |
看不太明白,还是继续我们路由得了
5:原来_Creator是CComObjectRootBase的静态成员函数,它可是COuter的一个基类啊,所以才可以这样写而不会编译出错。看看它的实现吧:
static HRESULT WINAPI _Creator(void* pv, REFIID iid, void** ppvObject,DWORD) { _ATL_CREATORDATA* pcd = (_ATL_CREATORDATA*)dw; return pcd->pFunc(pv, iid, ppvObject); } struct _ATL_CREATORDATA { _ATL_CREATORFUNC* pFunc; }; typedef HRESULT (WINAPI _ATL_CREATORFUNC)(void* pv, REFIID riid, LPVOID* ppv); template < class Creator > _ATL_CREATORDATA _CComCreatorData::data = {Creator::CreateInstance}; |
源代码都列出来了,不用我多说,大家也都能看懂了。继续路由吧
6:绕了一大圈,现在我们调用的应该是
CComInternalCreator<...>::CreateInstance template < class T1 > class CComInternalCreator { public: static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv) { ATLASSERT(*ppv == NULL); HRESULT hRes = E_OUTOFMEMORY; T1* p = NULL; ATLTRY(p = new T1(pv)) if (p != NULL) { p->SetVoid(pv); p->InternalFinalConstructAddRef(); hRes = p->FinalConstruct(); p->InternalFinalConstructRelease(); if (hRes == S_OK) hRes = p->_InternalQueryInterface(riid, ppv); if (hRes != S_OK) delete p; } return hRes; } }; |
同我们所见到的大多数Creator类一样,它也只有一个静态CreateInstance函数。现在我们终于可以创建我们分割组件了,它不是CTearOff1,它也是经了一层包装的,是 CComTearOffObject! 现在我们再来看看它的构造函数干了些什么事:
CComTearOffObject(void* pv) { ATLASSERT(m_pOwner == NULL); m_pOwner = reinterpret_cast< CComObject< Base::_OwnerClass >* >(pv); m_pOwner->AddRef(); } |
还记得CTearOff1是从CComTearOffObjectBase继承的吗,这个基类包含了一个成员变量m_pOwner,现在它被赋值为指向它的外部对象的指针了。
7.现在终于把这个实现分割接口的组件创建了,剩下的在CTearOff1中查询ITearOff1的工作已经是重复劳动了,不再赘述。
执行pTear1->QueryInterface(ITearOff1, (void **)&pTear2)
一个实现分割接口的组件有可能包含多个分割接口,我们来检测一下它的查询过程。
函数堆栈二:
4.............. 3.COuter::_InternalQueryInterface(...) 2.ATL::CComObject< COuter >::QueryInterface(...) 1.ATL::CComTearOffObject< CTearOff1 >::QueryInterface(...) |
解释:
1:
STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) { return m_pOwner->QueryInterface(iid, ppvObject); } |
还记得我们创建的分割组件是CComTearOffObject< CTearOff1 >吗?现在执行查询操作的是它的成员函数。它的实现很简单,事实上它什么也没做,仅仅是把它交给它的外部对象(即CComObject< COuter >)去做了。还记得m_pOwner是在构造函数里赋值的吧。现在是否感到有些不妙呢?呵呵
2、3:果然,现在已经不用再看下去了,剩下的将是重复我们在调用第一条查询操作所做的一切。这个过程很简单,但它也隐含说明了一点:若对一个实现分割接口的组件每查询一次它的接口,就会调用一个新的实例!!!在上例中,最后的结果pTear2和pTear1 是不一样的!!这显然是浪费!
在下一节中,我们将介绍一个可以解决这个问题的宏!