ATL接口映射宏详解[2]

5:

CComCreator::CreateInstance(void* pv, REFIID riid, LPVOID* ppv)
{
    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->QueryInterface(riid, ppv);
        if (hRes != S_OK)
            delete p;
    }
}

  注意这里的T1是CComObjectCached< ATL::CComClassFactory >,这是我们给CComCreator 的模板参数。我们又一次看到了我们熟悉的操作符'new'!直到现在我们终于创建了组件的类厂。但还没完,继续往下走,看看SetVoid(pv)里干了些什么?

void CComClassFactory::SetVoid(void* pv)
{
    m_pfnCreateInstance = (_ATL_CREATORFUNC*)pv;
}

  大家还记得我们曾经把CMyObject::_CreatorClass::CreateInstance作为参数传给 pEntry->pfnGetClassObject(...)吧,当时我们不明白是怎么回事,现在已经豁然开朗!原来是类厂需要它来创建组件对象!虽然我们只是从字面意思猜出这一点,但实际上也正如我们所预料的那样,在  CComClassFactory::CreateInstance(...)中,我们看到了m_pfnCreateInstance(pUnkOuter, riid, ppvObj);现在一切都已经明白了, ATL为我们创建类厂而作的层层包装我们都已经打开,剩下的创建组件的过程已经是我们很熟悉的过程了!
  但是现在还没有完,我们还需要为类厂对象查询一个IUnknown指针,这个指针就存在我们在前面所看到的pEntry->pCF中。

  6:

  STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
{return _InternalQueryInterface(iid, ppvObject);}

  现在调用的是CComObjectCached::QueryInterface,至于这个类有何特别之处,我们现在好象还不需要知道,我也很累的说,呵呵。

  7:

HRESULT _InternalQueryInterface(REFIID iid, void** ppvObject) /
{ return InternalQueryInterface(this, _GetEntries(), iid, ppvObject); }

  所有的类的_InternalQueryInterface(...)都是在BEGIN_COM_MAP中定义的。 CComObjectCached没有BEGIN_COM_MAP宏,所以现在调用的是CComClassFactory的。注意把this指针和接口映射数组_GetEntries()传给了InternalQueryInterface(), 这是InternalQueryInterface(...)实现查询的依据。在BEGIN_COM_MAP(x)中定义了一个静态的接口映射数组:
_ATL_INTMAP_ENTRY _entries[];

  每一个接口映射宏实际上都是向这个数组中增加了一项。一个接口映射宏包括三个部分:接口的IID号、偏移值(大部分时候下)、需要执行的函数,对一般接口来说不用执行其他函数。_GetEntries()就是返回这个数组。还有一些细节问题以后再说。

8:

static HRESULT WINAPI InternalQueryInterface(void* pThis,
    const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject)
{
    ...
    HRESULT hRes = AtlInternalQueryInterface(pThis, pEntries, iid, ppvObject);
...
}

  现在调用的是CComObjectRootBase::InternalQueryInterface(...)

  9:现在我们终于到了QueryInterface的鼻祖了。

  AtlInternalQueryInterface(...)是整个查询过程的终点,它遍历接口映射表,并根据每一项做出相应的动作。ATL中的消息映射宏有很多种,相应的动作也很多,但现在我们不管那些,现在我们要做的就是查到一个IUnknown接口,这很容易,我们甚至不需要遍历接口映射表。

ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis,
    const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject)
{
    ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY);
    if (ppvObject == NULL)
        return E_POINTER;

    *ppvObject = NULL;
    if (InlineIsEqualUnknown(iid)) // use first interface
    {
        IUnknown* pUnk = (IUnknown*)((int)pThis+pEntries->dw);
        pUnk->AddRef();
        *ppvObject = pUnk;
        return S_OK;
    }
    ...//还有一大堆呢,但现在用不上,就节省点空间吧
}

  这里有一个规定,接口映射表的第一个接口必须是_ATL_SIMPLEENTRY型的。至于为什么有这个要求,以及pThis+pEntries->dw是什么意思,我们以后再说吧,那也是一堆问题。总之,我们现在如愿以偿轻松的获得了我们所需要的类厂对象以及IUnknown指针。
  4:我差一点以为我们可以胜得返回到第一步了,但在ATL::AtlModuleGetClassObject 处却又停了下来,看看它的源码,原来还要再通过我们刚获得的IUnknown指针查询 IClassFactory指针。又是一通相同的调用,从第6步到第9步一模一样,我们将进行相同的调用。但注意在第9步中,我们这回查的不再是IUnknown指针了,所以我们需要看看我刚才还没列出的代码,但这留到下一次函数堆栈再看吧

  1:终于终于我们已经完成了创建类厂对象的全部操作,现在我们要做的就是我们熟悉的调用类厂对象的CreateInstance(...)函数创建组件的过程了。正如我们所见到的,现在OLE开始调用CComClassFactory::CreateInstance()了,我们还没忘记,在类厂对象中保留了创建组件用的CreateInstance()函数, 这个过程已经很明朗了。

  2.不用再重复了吧,看第4步。

  3.不用再重复了吧,看第4步。

  4.如果继续路由下去的话,我们的堆栈还可以很长,但这只是重复的枯躁的劳动。我就不继续走下去了,我也很累的说,唉。

  函数调用堆栈二:

  0:............
  5.ATL::AtlInternalQueryInterface(...)
  4.ATL::CComObjectRootBase::InternalQueryInterface(...)
  3.CMyObject::_InternalQueryInterface(...)
  2.ATL::CComObject< CMyObject >::QueryInterface(...)
  1.pUnk->QueryInterface(IID_IMyObject, (void **)&pMyObject);(客户端)

  解释如下:

  1.我们通过刚刚获得的组件对象的IUnknown接口指针来查询IMyObject指针,这才是我们真正需要的指针。

  2.还记得我们说过ATL真正创建的组件并不是CMyObject,而是CComObject,CComAggObject 或CComPolyObject,这里我们创建的是CComObject.所以理所当然我们要调用 CComObject::QueryInterface(...),而确实CComObject也实现了这个函数。

 
  STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
{return _InternalQueryInterface(iid, ppvObject);}
它只是简单地调用_InternalQueryInterface(...),我们也说过,只有类里面申明了BEGIN_COM_MAP宏才会有_InternalQueryInterface(...),所以现在执行转到了它的父类CMyObject中去,所以将调用CMyObject::_InterfaceQueryInterface(...)

  3.以后的调用我们已经很熟悉了,还用我再说一遍吗,呵呵

  4.这个调用我们也很熟悉了,不用多说了吧

  5.现在我们将要查询的是一个非IUnknown接口,所以我们来看看我们以前没列出的代码

ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis,
    const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObject)
{
    //确保接口映射的第一项是个简单接口
    //若是查询IUnknown接口,执行相应的操作
    //以下将遍历接口映射表,试图找到相应的接口
    while (pEntries->pFunc != NULL)
    {
        BOOL bBlind = (pEntries->piid == NULL);
        if (bBlind || InlineIsEqualGUID(*(pEntries->piid), iid))
        {
            //_ATL_SIMPLEMAPENTRY就表明是个简单接口
            if (pEntries->pFunc == _ATL_SIMPLEMAPENTRY) //offset
            {
                ATLASSERT(!bBlind);
                IUnknown* pUnk = (IUnknown*)((int)pThis+pEntries->dw);
                pUnk->AddRef();
                *ppvObject = pUnk;
                return S_OK;
            }
            else //如果不是一个简单接口,则需要执行相应的函数
            {
                HRESULT hRes=pEntries->pFunc(pThis,iid,ppvObject,pEntries->dw);
                if (hRes == S_OK || (!bBlind && FAILED(hRes)))
                    return hRes;
            }
            }
            pEntries++;
        }
        return E_NOINTERFACE;
    }
}

  函数的逻辑很清楚,只有两点可能不太理解,一个是 (IUnknown*)((int)pThis+pEntries->dw)是什么意思,另一个是pEntries->pFunc到底 要干些什么事。前一个问题将在讲述COM_INTERFACE_ENTRY2中讲述,后一个问题将在以后讲述不同类型的接口时分别解释。饭总是要一口一口吃的嘛,呵呵。   现在我们只需关心一下我们的IMyObject是怎么被查找的。看一下它的宏我们把COM_INTERFACE_ENTRY(IMyObject)解开以后形式为:

{&_ATL_IIDOF(IMyObject), //得到IMyObject的IID值
offsetofclass(IMyObject, CMyObject), //定义偏移量
_ATL_SIMPLEMAPENTRY},//表明是个简单接口

  同样对于offsetofclass(IMyObject, CMyObject)我们也将留到下一次再讲。根据这个结构,我们很容易就能获得IMyObject接口指针。
  0:OK,it is over.依次退栈返回。
  其实这次查询发生的过程在刚才的调用序列中也发生了,当查询IClassFactory接口时就有类似的过程,但还是把它单独提了出来,只为了看看典型的情形,呵呵。
阅读更多
个人分类: COM学习
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭