ATL接口映射宏详解[5]

原创 2004年07月19日 11:16:00
四.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的真正功能。感觉实现这个宏也许不应这么麻烦的。

ATL接口映射宏详解

这几天看了看ATL的接口映射宏,不知不觉看得比较深入了,突然就萌发了把它写出来的想法。ATL中定义了很多接口映射宏,有几个还是比较重要的,虽然好象没有必要把它所有的细节都弄得很清楚,但深入学习的过程中...
  • FrankieWang008
  • FrankieWang008
  • 2014年06月13日 17:20
  • 1126

ATL接口映射宏详解

ATL接口映射宏详解(下)   五.COM_INTERFACE_ENTRY_AGGREGATE(iid, punk) 参ATL例程COMMAP       这一节中将介绍ATL中用于聚...
  • Andeewu
  • Andeewu
  • 2013年03月27日 09:20
  • 542

ATL字符串转换宏

有比MultiByteToWideChar和WideCharToMultiByte更简单的字符串转换宏,你相信吗? 头文件 d:/program files/microsoft visual ...
  • u011135902
  • u011135902
  • 2016年07月20日 14:21
  • 779

通过ATL实现IUnknown接口

每个COM组件需要实现这三个功能 1. 实现IUnknown接口 (通过 CComObjectRootEx 实现) 2. 实现一个类工厂,支持组件的创建 (通过 CComCoClass 实现) ...
  • wishfly
  • wishfly
  • 2013年09月27日 10:10
  • 820

ATL-连接点和接口方法的使用

前言在做COMDLL, 用MFC测试程序插入注册好的ATL控件. 测试连接点的添加, 控件方法的添加,测试程序被控件调用连接点函数, 测试程序去调用控件的接口方法.效果图工程下载点srcUserLo...
  • LostSpeed
  • LostSpeed
  • 2016年04月20日 12:25
  • 1242

MFC消息映射笔记

大家有没有思考过当一个消息出现,应用程序框架是如何将消息与对象建立关系的?1.消息宏\quad为了支持消息映射,MFC提供了3种宏。1.1消息映射的声明和分解宏消息映射的声明和分界宏包含在CCmdTa...
  • q__y__L
  • q__y__L
  • 2016年05月03日 19:45
  • 1208

使用ATL创建简单ActiveX控件(三) —— 添加连接点

创建过程以VS2010为例,分三篇(创建ATL项目、添加方法/属性和枚举、添加连接点)演示。本篇演示添加连接点。 传送门: 《使用ATL创建简单ActiveX控件(一) —— 创建ATL项目 》 ...
  • mrxyz098
  • mrxyz098
  • 2015年09月20日 14:06
  • 2852

ATL--创建简单的ATL之dll工程,添加类和类的接口并在MFC中调用

开发环境 Windows Server 2012  VS2010 Sp1 番茄助手 创建ATL简单dll工程 1、打开VS2010,新建ATL COM 项目,步骤:“文件” “新建” “项目”,选择“...
  • sakawa_x
  • sakawa_x
  • 2017年06月07日 07:39
  • 1062

ATL实现一个组件多个dual接口,multidisp

最近想自己写个按键精灵的插件,于是接触到这个问题: 怎么在一个组件里实现两个自动化接口。 主要针对的ATL,MFC貌似没这个问题,具体MFC是怎么实现的自己没有深究。 按键精灵的插件会在一...
  • jzkdl
  • jzkdl
  • 2014年11月29日 03:42
  • 1949

ATL字符串转换宏

有比MultiByteToWideChar和WideCharToMultiByte更简单的字符串转换宏,你相信吗? 头文件 d:/program files/microsoft visual ...
  • earbao
  • earbao
  • 2013年09月15日 00:58
  • 5141
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:ATL接口映射宏详解[5]
举报原因:
原因补充:

(最多只允许输入30个字)