Programming 版 (精华区)

发信人: lofe ()感激生活(), 信区: Programming
标  题: ATL接口映射宏详解--(1)
发信站: 哈工大紫丁香 (Sun Sep  3 08:15:23 2000), 转信

以后将分别介绍ATL中各个形式为COM_INTERFACE_ENTRY_XX的接口映射宏。
并将按照从易到难的顺序讲解,每一部分都将建立在前一部分的基础上。
每一部分都将通过分析实际的调用函数堆栈来进行分析,堆栈的写法是从下向上。
文中所涉及的代码都为略写,只列出相关部分。


一、COM_INTERFACE_ENTRY(x)
首先我们从一个最典型的应用开始:
定义一个最简单的ATL DLL:
class ATL_NO_VTABLE CMyObject :
        public CComObjectRootEx<CComSingleThreadModel>,
        public CComCoClass<CMyObject, &CLSID_MyObject>,
        public IDispatchImpl<IMyObject, &IID_IMyObject, &LIBID_TEST2Lib>
{
.....
BEGIN_COM_MAP(CMyObject)
        COM_INTERFACE_ENTRY(IMyObject) //一个双接口
        COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
.....
};

编写一段最简单的查询接口代码:
IUnknown        *pUnk;
IMyObject       *pMyObject;
CoCreateInstance(CLSID_MyObject, NULL, CLSCTX_INPROC_SERVER,
                IID_IUnknown, (void **)&pUnk);
pUnk->QueryInterface(IID_IMyObject, (void **)&pMyObject);

执行客户代码,首先我们看看组件对象是如何被创建的。
函数调用堆栈一:
4...........
3.ATL::CComCreator<ATL::CComObject<CMyObject> >::CreateInstance(...)
2.ATL::CComCreator2<ATL::CComCreator<ATL::CComObject<CMyObject> >,
    ATL::CComCreator<ATL::CComAggObject<CMyObject> > >::CreateInstance(...)
1.ATL::CComClassFactory::CreateInstance(...)
4.ATL::AtlModuleGetClassObject(...)
9.ATL::AtlInternalQueryInterface(...)
8.ATL::CComObjectRootBase::InternalQueryInterface(...)
7.ATL::CComClassFactory::_InternalQueryInterface(...)
6.ATL::CComObjectCached<ATL::CComClassFactory>::QueryInterface(...)
5.ATL::CComCreator<ATL::CComObjectCached<ATL::CComClassFactory> >::
           CreateInstance(...)
4.ATL::AtlModuleGetClassObject(...)
3.ATL::CComModule::GetClassObject(...)
2.DllGetClassObject(...)
1.CoCreateInstance(...)(客户端)


解释如下:
1:CoCreateInstance(CLSID_MyObject, NULL, CLSCTX_INPROC_SERVER,
                IID_IUnknown, (void **)&pUnk);
  其内部将调用OLE API函数CoGetClassObject(), 而CoGetClassObject则会通过
  LoadLibrary(...)装入DLL,并调用DLL中的DllGetClassObject()函数。
2:STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
  {
    return _Module.GetClassObject(rclsid, riid, ppv);
  }
  其中值得注意的是_Module变量,在DLL中定义了全局变量:
  CComModule _Module;
  ATL通过一组宏:
  BEGIN_OBJECT_MAP(ObjectMap)
     OBJECT_ENTRY(CLSID_MyObject, CMyObject)
  END_OBJECT_MAP()
  #define BEGIN_OBJECT_MAP(x) static _ATL_OBJMAP_ENTRY x[] = {
  #define END_OBJECT_MAP()   {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}};
  #define OBJECT_ENTRY(clsid, class) {&clsid, class::UpdateRegistry,
          class::_ClassFactoryCreatorClass::CreateInstance, //关键
          class::_CreatorClass::CreateInstance,
          NULL, 0, class::GetObjectDescription,
          class::GetCategoryMap, class::ObjectMain },
  生成一个静态全局_ATL_OBJMAP_ENTRY型数组:ObjectMap[];
  然后ATL又在
  BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
  {
     .....
     _Module.Init(ObjectMap, hInstance, &LIBID_TEST2Lib);
     .....
  }初始化_Module //注意在有的情况下是在InitInstance()中初始化_Module
  那么_Module初始化都做了些什么呢,其实他什么也没做,在CComModule::Init中,它
  调用AtlModuleInit(_ATL_MODULE* pM, _ATL_OBJMAP_ENTRY* p, HINSTANCE h),在其
  中关键的只有一句:pM->m_pObjMap = p;可见_Module仅仅是把这个全局对象映射数组
  ObjectMap[]给存了起来。那么为什么可以通过_Module.GetClassObject得到类厂呢?
  其实关键在于我们的组件CMyObject继承的又一个基类CComCoClass!
  在CComCoClass中缺省定义了一个宏DECLARE_CLASSFACTORY()而
  #define DECLARE_CLASSFACTORY() DECLARE_CLASSFACTORY_EX(CComClassFactory)
  #define DECLARE_CLASSFACTORY_EX(cf)
    typedef CComCreator< CComObjectCached< cf > > _ClassFactoryCreatorClass;
  CComCreator,CComObjectCached我们暂且不管,但一看到CComClassFactory,顾名思
  义,我们就知道我们要的类厂终于出现了!每个组件内部原来都有一个类厂对象。
  绕了一大圈,我们现在已经知道了_Module中包含了我们所要的每个组件的类厂对象,
  这对目前来说已经足够了,现在继续路由下去!
3:HRESULT CComModule::GetClassObject(REFCLSID rclsid,REFIID riid,LPVOID* ppv)
  {
        return AtlModuleGetClassObject(this, rclsid, riid, ppv);
  }
  CComModule::GetClassObject的实现非常简单,仅仅是调用ATL的API函数。
4:ATLINLINE ATLAPI AtlModuleGetClassObject(_ATL_MODULE* pM, REFCLSID rclsid,

                 REFIID riid, LPVOID* ppv)
  {
     _ATL_OBJMAP_ENTRY* pEntry = pM->m_pObjMap;//从_Module中取出对象映射数组

     while (pEntry->pclsid != NULL)
     {  
        if ((pEntry->pfnGetClassObject != NULL) && InlineIsEqualGUID(rclsid,
                           *pEntry->pclsid))
        {
          if (pEntry->pCF == NULL)
          {
             if (pEntry->pCF == NULL)
                hRes = pEntry->pfnGetClassObject(pEntry->pfnCreateInstance,
                                    IID_IUnknown, (LPVOID*)&pEntry->pCF);
          }
          if (pEntry->pCF != NULL)
                hRes = pEntry->pCF->QueryInterface(riid, ppv);
              break;
        }
          pEntry = _NextObjectMapEntry(pM, pEntry);
     }
  }
  现在好象已经有点看不懂了,看来我们得看看_ATL_OBJMAP_ENTRY的结构了
  struct _ATL_OBJMAP_ENTRY
  {
        const CLSID* pclsid;
        HRESULT (WINAPI *pfnUpdateRegistry)(BOOL bRegister);
        _ATL_CREATORFUNC* pfnGetClassObject;
        _ATL_CREATORFUNC* pfnCreateInstance;
        IUnknown* pCF;
        DWORD dwRegister;
        _ATL_DESCRIPTIONFUNC* pfnGetObjectDescription;
        _ATL_CATMAPFUNC* pfnGetCategoryMap;
  }
  pclsid很清楚就代表着我们组件的CLSID;pfnGetClassObject我们也已经知道了它就
  是CMyObject::_ClassFactoryCreatorClass::CreateInstance(我们组件所包含的类
  厂对象的CreateInstance函数);pCF我们也可以猜出它是指向这个类厂的IUnknown指
  针,代表这个类厂对象是否被创建过,若类厂对象已经存在,就不用再创建新的类厂
  对象了。现在就剩下pfnCreateInstance我们还不明白怎么回事。其实答案还是在
  CComCoClass中!
  在CComCoClass中缺省定义了宏DECLARE_AGGREGATABLE(x),这个宏表示这个组件既可以
  是聚集的也可以是非聚集的,关于聚集的概念我们暂且不理,先看它的定义:
  #define DECLARE_AGGREGATABLE(x) public:\
        typedef CComCreator2< CComCreator< CComObject< x > >, \
               CComCreator< CComAggObject< x > > > _CreatorClass;
  我们看到了一个熟悉的字符串_CreatorClass, 原来这还有一个组件包含的对象。但还
  有一个问题我们没有搞清楚,就是为什么_ClassFactoryCreator和_CreatorClass后面
  都要跟着一个CreateInstance? 看来我们必须先来看看CComCreator是个什么东西了。
  template <class T1>
  class CComCreator
  {
  public:
        static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv)
        {.....
        }
  };
  原来它里面只有一个CreateInstance函数,我们现在终于大体明白_ClassFactoryCre
  atorClass::CreateInstance表示什么意思了,它就代表CComClassFactory::CreateIn
  stance(..)吧,差不多就是这样了。那我们再来看看CComCreator2有什么不同:
  template <class T1, class T2>
  class CComCreator2
  {
  public:
        static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv)
        {
            return (pv == NULL) ?
                        T1::CreateInstance(NULL, riid, ppv) :
                        T2::CreateInstance(pv, riid, ppv);
        }
  };
  这个类与CComCreator很类似,都只有一个CreateInstance成员函数,从_CreatorClass
  中我们可以知道它实际上包含两个类CComObject,CComAggObject的CreateInstance函
  数(通过CComCreator),其中CComObject用于非聚集对象,CComAggObject用于聚集对象
  根据情况它建立相应的对象。(ATL中实际生成的组件对象不是CMyObject,而是
  CComObject,CComAggObject或CComPolyObject对象,这个概念很重要,但现在暂且不谈)
  现在我们对AtlModuleGetClassObject(...)基本已经知道是怎么回事了,它就是根据
  存在对象映射数组中的创建类厂的函数的地址来创建类厂。pfnGetClassObject以及
  pfnCreateInstance我们基本上都已经知道是怎么回事了,但还有一个问题为什么要
  把pEntry->pfnCreateInstance作为pEntry->pfnGetClassObject(...)中的一个参数
  传递?答案在下面呢,让我们继续路由下去!
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接口时就
  有类似的过程,但还是把它单独提了出来,只为了看看典型的情形,呵呵。


          -------------未完待续----------------
今天第一次写,没想到这么累,花了这么多时间,唉,看来还得熬两次夜了

--
才疏学浅,胡言乱语;不对之处,敬请指正。



                路漫漫兮,其修远。
                吾将上下而求索。

※ 修改:.haojs 于 Sep  3 08:12:59 修改本文.[FROM: bbs.hit.edu.cn]
--
※ 转寄:.武汉白云黄鹤站 bbs.whnet.edu.cn.[FROM: bbs.hit.edu.cn]

--
☆ 来源:.哈工大紫丁香 bbs.hit.edu.cn.[FROM: haojs.bbs@bbs.whnet.]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:213.049毫秒