ATL接口映射宏详解[5]

来源:互联网 发布:centos 7 卸载软件 编辑:程序博客网 时间:2024/05/19 23:53
四.COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(iid, x, punk)

  这个宏与上一节所讲的COM_INTERFACE_ENTRY_TEAR_OFF宏最主要的不同就在于,当查询分割对象中其他接口时,不会再新建新的对象。下面还是先看看它的典型用法:

class CTearOff2:
    public IDispatchImpl< ITearOff2, &IID_ITearOff2, &LIBID_COMMAPLib >,
    public CComTearOffObjectBase< COuter >
{
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< IUnknown > m_pUnkTearOff2;
    .....
};
  CTearOff2实现了分割接口ITearOff2,它的类定义与上一节所看见的CTearOff1一模一样可见不管是哪种分割接口,实现都是一样的,不同的地方在于COuter。在COuter中增加了一个成员变量m_pUnkTearOff2作为宏的一个参数。
  我们继续用老办法跟踪它的内部执行过程,假设pOuter是已经获得的组件COuter有接口IOuter指针。

  执行pOuter->QueryInterface(IID_ITearOff2, (void **)&pTear1);

  函数堆栈一:

  9.CTearOff2::_InternalQueryInterface(...)
  8.ATL::CComCachedTearOffObject< CTearOff2 >::QueryInterface(...)(第二次调用)
  9.ATL::CComCachedTearOffObject< CTearOff2 >::QueryInterface(...)
  8.ATL::CComCreator< ATL::CComCachedTearOffObject< CTearOff2 > >::CreateInstance()
  7.ATL::CComObjectRootBase::_Cache(...)
  6.COuter::_Cache(...)
  5.ATL::AtlInternalQueryInterface(...)
  4.ATL::CComObjectRootBase::InternalQueryInterface(...)
  3,COuter::_InternalQueryInterface(...)
  2.ATL::CComObject< COuter >::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)/
       ?gt;::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 < class Creator, DWORD dwVar >
_ATL_CACHEDATA _CComCacheData< Creator, dwVar >::data = {dwVar, Creator::CreateInstance};

  CComCreator我们在前面已经见过它的定义了,它只有一个成员函数

CreateInstance.
template < class contained >
class CComCachedTearOffObject :
    public IUnknown,
    public CComObjectRootEx< contained::_ThreadModel::ThreadModelNoCS >
{
public:
    typedef contained _BaseClass;
    CComCachedTearOffObject(void* pv)
: m_contained(((contained::_OwnerClass*)pv)->GetControllingUnknown())
    {
        m_contained.m_pOwner = reinterpret_cast< CComObject< contained::_OwnerClass >* >(pv);
    }

    CComContainedObject< contained > 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< CComCachedTearOffObject < x > >::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< CComObject< contained::_OwnerClass >* >(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< CTearOff2 >::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< CTearOff2 >::QueryInterface(...)
  8.ATL::CComObjectRootBase::_Cache(...)
  7.COuter::_Cache(...)
  6.ATL::AtlInternalQueryInterface(...)
  5.ATL::CComObjectRootBase::InternalQueryInterface(...)
  4.COuter::_InternalQueryInterface(...)
  3.ATL::CComObject< COuter >::QueryInterface(...)
  2.ATL::CComObjectRootBase::OuterQueryInterface(...)
  1.ATL::CComContainedObject< CTearOff2 >::QueryInterface(...)
  解释:

  1:第一步就可能使我们迷惑了,为什么执行的是CComContainedObject::QueryInterface

  在上一节中,执行的是ATL::CComTearOffObject< CTearOff1 >::QueryInterface(...),所以我们也自然而然的猜想,这里应该执行的是CComCachedTearOffObject的函数。但是来看看CComCachedTearOffObject的定义:

template < class contained >
class CComCachedTearOffObject :
    public IUnknown,
    public CComObjectRootEx< contained::_ThreadModel::ThreadModelNoCS >
{ ... };
  原来CComCachedTearOffObject没有从contained类(在这里就是CTearOff2)中继承,而 CComTearOffObject却是从CTearOff1继承的!所以我们刚才得到的pTear1就不可能是CComCachedTearOffObject的对象。而实际上,CComContainedObject是从CTearOff2继承的,在上面的函数堆栈中第9步查询ITearOff2接口时,把工作交给了m_contained, 这是个CComContainedObject< CTearOff2 >对象,所以实际上最后查询得到的ITearOff2 指向的是CComContainedObject< CTearOff2 >对象。所以现在执行的会是 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< CTearOff2>::QueryInterface(...),在上一个函数堆栈中的第9步中我们已经看到了这个QueryInterface(...)的代码,它把查询工作交给m_contained._InternalQueryInterface(.),其实因为CComContainedObject中没有定义BEGIN_COM_MAP宏,所以也没有定义_InternalQueryInterface(),所以实际上调用的是它包含的类的函数,即CTearOff2::_InternalQueryInterface(...)

  10-12:以下的工作就很简单了,不再赘述。

  总结:

  COM_INTERFACE_ENTRY_CACHED_TEAR_OFF是个相对比较麻烦的宏,它与上一节介绍的宏相比不同之处就在于创建分割接口对象的过程只用进行一次,如果对象已经创建,则下一次查询该对象的接口时不会再创建一个新的分割对象。为了达到这个目的,它在外部对象中包含了一个IUnknown指针,并在第一次创建分割对象时查询这个IUnknown指针,这样就可以通过判断这个指针是否为空来知道这个分割对象是否已经创建,从而决定是否创建新的分割对象,并通过它去查询分割对象内其它接口。这里特别需要注意的是,实际上有两个对象被创建,一个是CComCachedTearOffObject< CTearOff2 >,另一个是 CComContainedObject< CTearOff2 >。并且第一个对象内部实现了第二个对象,真正的查询工作也是交给第二个对象去做。COuter::m_pUnkTearOff2是前面一个对象的IUnknown指针,当用它去查询ITearOff2时,实际上是交给了其内部对象m_contained去做了,这在第8、9步可以看得很清楚。
  终于把这个宏讲完了,我感觉这个宏可能是ATL接口映射宏中实现最复杂的了,其实它并没有利用到CComContainedObject的真正功能。感觉实现这个宏也许不应这么麻烦的。