• ATL揭秘之“对象创建”篇


    ATL揭秘之“对象创建”篇

    总结:
    客户调用coCreateInstance(),
       该函数内部首先CoGetClassObject通过注册表机制,找到相应的服务器,并且调用服务器的DllGetClassObject函数来获得类厂
      然后调用类厂的 CreateInstance方法来创建COM对象了
    对于ATL COM来说
      DllGetClassObject会调用 CComModule的成员函数GetClassObjectGetClassObject遍历结构数组,找到相应的CLSID对应的_ATL_OBJMAP_ENTRY结构。ATL会先检查结构中的pCF,这是ATL缓存的类厂对象指针,如果不为空,则可以直接利用该指针来创建COM对象,如果为空,则调用结构中的pfnGetClassObject函数指针,创建相应的类厂对象并且把类厂对象的指针缓存到pCF成员数据中。最后实际调用的GetClassObject为static函数class::_ClassFactoryCreatorClass::CreateInstance, 找到类厂后,接着调用类厂的createinstance, 似乎也是在这个表里找class::_CreatorClass::CreateInstance,
       CComCreator::CreateInstance这样写的...

    http://www.diybl.com/ 2007-10-9  网络 点击:  [ 评论 ]
    文章搜索:    【点击打包该文章】
    【本站开通在线QQ讨论群】

    1         问题

    当我们用VC++ ATL工程创建了一个COM工程,实现了一个自己的COM对象,又在另一个程序中CoCreateInstance这个COM对象时,不知你是否想过这样的问题:COM对象是用C++类对象实现的,但是,我们从来没有在自己的代码中创建这些C++类对象——比如,“new”这些对象。那么,实现COM对象的C++对象是由谁,何时,以及如何创建的呢?

    当然,简单而且正确的回答是:ATL在幕后帮助我们完成了这些工作。如果你不想了解ATL的工作细节,这样的回答应该是足够了。然而,ATL本身的思想就是“白盒”操作,想要用好ATL,就应该尽量多地了解ATL的工作细节。所以,搞清楚这个问题还是很有必要的。

    想到这个问题后,出于懒惰的天性,我首先上网,试图找到别人对于这个问题的讲述,然而,大家要么讨论C++对象,要么讨论ATL其他的机制,似乎没有人特别关注ATL COM对象的创建过程,更比较少有人留心ATL如何将COM对象创建过程转换到C++对象创建过程上。

    在研究这个问题的过程中,我逐渐发现这个问题很有意思,对这个问题的完整回答涉及了ATL相当多的基础结构。弄清楚了这个问题,对ATL的了解也会加深不少。

    下面,我们就一起开始ATL对象创建揭秘之旅。

    2          “对象”探讨

    既然谈“对象创建”,则有必要对“对象”这个概念作一点讨论。在实际工作中,我感觉不少人对“对象”这个概念有不少误解;对“COM对象”也没有清晰的认识。

    2.1      对象性质

    这似乎是老生常谈了。对象性质,不就是“封装、继承、多态”这三个陈词滥调吗?然而,孔老夫子教导我们说:“温故而知新”。真理往往就蕴含在陈词滥调中。经过这些年的软件生涯,我对这句“陈词滥调”似乎有了更多地理解:

    首先,“对象性质”是个独立的概念,也就是说,凡是具备了这个性质的东西就可以被称作对象。因此,一来“对象”不一定要用面向对象的语言编写,二来“对象”也可以具备各种环境下的语义——面向对象语言生成的对象是“编程语言”语义下的对象,如“C++对象”; 面向组件的开发生成的对象是“组件环境”语义下的对象,如“COM对象”。

    其次,对象性质中的“继承”、“多态”需要好好斟酌。

    什么是“继承”?是不是一定要用“CMyObject::CBaseObject”这样的语法才叫继承?当然不是,“继承”应该是对“对象层次结构”的有效处理。只要能够有效地处理对象层次结构,使低层对象能够自动具备高层对象的特性、行为,就应该可以被叫做“继承”。“CMyObject::CBaseObject”干的是什么事?不就是把CBaseObject的成员变量复制给了CMyObject,并且使CMyObject的对象能够调用CBaseObject的公有和保护方法吗?

    再说“多态”。C++语言说“多态”就是支持虚函数调用,这样讲对,但是局限在C++语言本身上。“虚函数调用”是某些语言的特性,难道没有虚函数的语言就无法支持多态了?其实“多态”这个词本身译得很好,直抒其意——“多姿多态”。“多态”本质上是“运行时决定行为”。只要能够在运行时才决定如何行动,而不是在编译时决定,就是“多态”。

    综合看来,“继承”和“多态”都不是面向对象语言的专利,其他的语言,只要能够通过某种机制实现这些特性,就可以实现“对象”。

    2.2      COM对象

    COM规范对于COM对象如何做到“封装、继承、多态”有自己的规定。该规定不依赖具体语言,不依赖具体的操作系统环境,所以,我们说COM规范是语言中立和平台中立的(当然,提供平台的人并不中立,这和规范的中立是两码事)。

    • “封装”:COM对象只处理行为封装,其工具是“接口”;
    • “继承”:COM的继承不是源代码级别,是二进制代码级别。COM对象提供了两种方式来继承对象的二进制代码——“包容”和“聚合”;
    • “多态”:COM的“运行时决定行为”能力来自不同对象实现同一接口。使用COM的统一方式——QueryInterface,我们可以找到不同COM对象对同一接口的实现,从而实现“运行时决定行为”。

    当然,COM对象除了这老三样之外,还要其他性质,其中最重要的就是对象的“生命周期管理”。“生命周期管理”通过AddRefRelease这两个“引用计数”函数实现。

     

    3         ATL COM对象

    ATL实现COM对象的基本思路是:针对不同的COM对象性质,分层处理。不同的ATL类层次处理特定的COM对象特性。

    ATL COM对象的层次结构如下图所示:

    从上图可以看到:

    • 最基础的类是CComObjectRootBase。该类除了提供InternalQueryInterface方法外,还实现了若干帮助方法可供最终派生类CComObject调用;
    • CComObjectRootEx是个模板类。该类根据不同的线程模型生成足够线程安全的InternalAddRefInternalRelease函数。为什么只提供一个CComObjectRootEx类呢?我觉得主要的原因是:CComObjectRootBase实现的InternalQueryInterface不涉及对类成员数据的线程保护,不涉及线程安全因素;CComObjectRootExInternalAddRefInternalRelease方法则和线程安全密切相关,故CComObjectRootEx有必要作为模板类实现。将这两个类揉到一起实现反而显得不清晰;
    • 我们自己定义的类直接从CComObjectRootEx继承,根据需要选择不同的线程模型;
    • ATL最后实际创建的COM对象是CComObjectCComAggObject等类的实例。这些类负责真正实现QueryInterfaceAddRefRelease方法,具体选择哪个类根据宏定义来决定。具体在哪里定义什么宏在4.3节会讲到。

    4         ATL COM对象创建——内部机制

    所谓内部机制,指的是类厂创建COM对象的过程。由于类厂也在COM对象的实现类中实现,所以,类厂对象创建相应COM对象的过程可以看作是COM对象的内部过程。

    正是在这个内部机制中,“COM对象创建”这个动作被转换到“C++对象创建”这个动作上。

    下图是对内部机制的简单勾勒:

    从这幅图中可以看到,内部创建主要涉及三个类的交互作用,它们是:CComCreatorCComClassFactoryCComCoClass。下面就对这三个类分别讲述。

    4.1      CComCreator——COM对象创建器

    COM规范要求用类厂来创建COM对象,其目的是使COM对象能够控制自己的创建过程(“类厂”设计模式的典型应用)。由于类厂对象本身也是一个COM对象,所以,ATL为了统一COM对象的创建过程,封装了一个CComCreator类。ATL CComCreator这个类的作用很单纯,正如其名字所表示的——创建COM对象。该类包装了一个CreateInstance静态方法(之所以是静态方法,因为该方法要放到_ATL_OBJMAP_ENTRY中,后面会讲到),正是在CComCreatorCreateInstance方法中,ATL COM对象创建被转换到具体的C++对象创建上。由于这个类如此重要,因此有必要列出这个类的实现:

     

    template <class T1>

    class CComCreator

    {

    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->QueryInterface(riid, ppv);

               if (hRes != S_OK)

                  delete p;

           }

           return hRes;

        }

    };

    其中,底色是黄色的那句代码就是实际创建C++对象的代码。看到熟悉的“new”了。

    从这个类是模板类也可以看出,ATL中所有的COM对象创建,最终其实都是由CComCreator类负责。比如,创建COM对象可以用CComCreator<CComObject>的形式;创建类厂类可以用CComCreator<CComClassFactory>的形式。后面那个CComCreatorCComClassFactory就是我们说的类厂类。

    4.2      CComClassFactory

    每个COM对象类都有一个自己的类厂类,专门负责创建该类的类对象。在ATL中,缺省的类厂类是CComClassFactory。类厂类也有一个CreateInstance方法,该方法调用类厂类保存的COM对象类的CComCreator的静态CreateInstance函数指针,创建相应的COM对象。

    4.3      CComCoClass

    CComCoClass是一个非常重要的ATL实现类。基本上我们自己的类都要从CComCoClass继承。为什么?因为CComCoClass定义了两个宏:

        DECLARE_CLASSFACTORY()

        DECLARE_AGGREGATABLE(T)

    前一个宏定义了_ClassFactoryCreatorClass——类厂类的创建者,该创建者可以使用不同的类厂类作为模板参数,为COM对象的创建过程提供了灵活性;后一个宏定义了_CreatorClass——COM对象类的创建者,该创建者使用CComObject类族的不同类作为模板参数,为COM对象QueryInterfaceAddRefRelease函数的实现方式提供了不同选择。

    通过继承CComCoClass,我们自己的类就继承了CComCoClass对类厂和最后生成类的实现。

    CComCoClass也有一个CreateInstance方法。该方法纯粹是对_CreatorClass::CreateInstance方法的包装。因为我们的类继承自CComCoClass,经过这个包装后,就可以直接以CUserClass::CreateInstance的方式来调用CComCreator::CreateInstance了。

     

    上图看到的三个CreateInstance方法,各有各的意义,这里总结一下:

    CComCreator::CreateInstance

    真正创建C++对象的所在

    CComClassFactory::CreateInstance

    调用_CreatorClass::CreateInstance

    CComCoClass::CreateInstance

    调用_CreatorClass::CreateInstance

     

    至此,估计大家一定有一个疑问:_CreatorClass::CreateInstance由类厂对象的CreateInstance调用;_ClassFactoryCreatorClass::CreateInstance又由谁来调用呢?这就是我们要进入的下一个论题:ATL COM对象创建的外部机制。

    5         ATL COM对象创建——外部机制

    所谓“外部机制”,指的是应用程序创建ATL COM对象类厂的过程。应用程序并不关心COM对象是MFC实现方式的还是ATL实现方式的,它永远使用CoCreateInstance这类API函数,通过类厂创建COM对象。在ATL下,应用程序对CoCreateInstance的调用,是如何转换到对ATL COM对象类厂CreateInstance方法的调用的呢?

    5.1      COM服务器

    COM对象不能凭空存在,它必须存在于操作系统的某种可执行文件中。由于只有Windows操作系统支持COM规范,很自然地,COM对象存在于Windows操作系统的可执行文件中。

    Windows操作系统的可执行文件,其格式主要有两种:EXEDLL。这里就不必要说这两种文件格式的区别了吧。如果不知道,这篇文章你估计也看不懂了。

    能够生成COM对象的可执行程序叫COM服务器。EXE是进程外服务器,DLL是进程内服务器。这里只讨论DLL的情况。由于DLL本身只能通过对外输出的函数与外界交互,所以,DLL作为COM服务器也是通过四个输出函数来体现其服务器的作用。这就是著名的四个函数:

    • DllRegisterServer
    • DllUnregisterSever
    • DllGetClassObject
    • DllCanUnloadNow


    COM服务器的工作机制可以用下图来表示:

     

    COM服务器的重要功能可以归纳为三个:

    • 管理服务器的生命周期;
    • 管理服务器和对象的注册;
    • 获得COM对象的类厂;

    我们可以看到,作为COM服务器的DLL,用四个函数来完成这三个方面的功能。四个输出函数的调用时机分别如下:

    • DllRegisterServerDllUnregisterServer:使用regsvr32程序注册和反注册服务器时;
    • DllCanUnloadNow:当调用CoFreeUnusedLibraries系统函数时;
    • DllGetClassObject:从函数的字面意思来理解,应该是创建COM对象时该函数被调用。而我们知道创建COM对象的API函数是CoCreateInstanceCoCreateInstance是个封装函数,它包装了对CoGetClassObject,以及相应类厂的CreateInstance函数的调用。CoGetClassObject通过注册表机制,找到相应的服务器,并且调用服务器的DllGetClassObject函数来获得类厂。一旦获得类厂对象,就可以调用类厂对象的CreateInstance方法来创建COM对象了。

    5.2      ATL COM服务器

    前面讲的是所有COM服务器都应该遵循的工作流程。不同的COM实现,实现这个流程的方式也不同。对于ATL来说,其具体的实现可以用下图简略体现:

     

    ATL COM服务器主要通过CComModule类和_ATL_OBJMAP_ENTRY结构来实施服务器管理。前面讲过,COM服务器的主要职能是三个:管理服务器生命周期、注册组件、获得COM对象的类厂,所以,CComModule的成员函数也围绕这三个方面。同样,_ATL_OBJMAP_ENTRY数据结构中的内容也紧紧围绕着这三个方面。由于本文讨论COM对象创建,所以,对服务器管理的讨论也局限在“获得COM对象的类厂”上。ATL COM服务器实现“获得COM对象的类厂”的步骤如下:

    1、 所有的ATL工程都会生成一个全局变量,其类型为CComModule,名字固定为_Module

    2、 DLL的四个输出函数内部都是调用_Module的成员函数来实现其功能。

    3、 CComModule提供了一系列成员函数来管理COM服务器,这些方法基本都工作在_ATL_OBJMAP_ENTRY结构数组上。

    4、 _ATL_OBJMAP_ENTRY结构内的成员基本上都是一些静态成员函数指针。最重要的函数指针是两个:pfnGetClassObjectpfnCreateInstance,它们都指向CComCreator的静态成员函数CreateInstance

    5、 _ATL_OBJMAP_ENTRY结构数组由三个宏配合定义:BEGIN_OBJ_MAPOBJECT_ENTRYEND_OBJ_MAP。其中,OBJECT_ENTRY宏比较重要,有必要在下面列出其定义:

     

    #define OBJECT_ENTRY(clsid, class) \

    {&clsid, class::UpdateRegistry, \

    class::_ClassFactoryCreatorClass::CreateInstance, \

    class::_CreatorClass::CreateInstance, NULL, 0, \

    class::GetObjectDescription, class::GetCategoryMap, \

    class::ObjectMain },

     

    注意黄底色部分。该宏用class的数据成员_ClassFactoryCreatorClassCreateInstance静态函数指针填充到pfnGetClassObject位置。用_CreatorClassCreateInstance静态函数指针填充到pfnCreateInstance位置。

    要找到一个特定的类厂,DllGetClassObject 将调用CComModule的成员函数GetClassObjectGetClassObject遍历结构数组,找到相应的CLSID对应的_ATL_OBJMAP_ENTRY结构。ATL会先检查结构中的pCF,这是ATL缓存的类厂对象指针,如果不为空,则可以直接利用该指针来创建COM对象,如果为空,则调用结构中的pfnGetClassObject函数指针,创建相应的类厂对象并且把类厂对象的指针缓存到pCF成员数据中。

    6         ATL COM对象创建——内外结合

    本文中,先讲了ATL COM对象本身,接着讲了ATL COM对象创建的内部机制——ATL COM对象的类厂如何创建ATL COM对象;再接着讲了ATL COM对象创建的外部机制——ATL COM服务器如何创建ATL COM对象的类厂。有个这几方面的了解之后,我们再把相关的知识结合起来,看一看ATL COM对象创建的统一场景。图示如下:

     

    图中左上部分是ATL COM对象本身;右上部分是ATL COM对象的创建;中下部分是ATL COM服务器对COM对象的管理。

    对每个部分的作用,本文各个部分已经有了具体描述,这里要强调的是图中标示为红色部分:_ATL_OBJMAP_ENTRY结构和CComCreator,正是通过它们,图中三个部分有机地联系到了一起,完成了ATL COM对象创建的任务。

    通观本文,没有给什么“示范代码”,而是力求从本人理解的COM原理的角度探讨ATLCOM对象创建机制。有可能这样的讨论在理论真正精深者看来不值一哂,然而,本人希望那些觉得ATL不好理解的人有了这次ATL COM对象创建过程探索的经历,能够感觉ATL好把握一些了,不再是若干莫名其妙的模板类的组合了。

     


    发信人: lostall (鸟人+衰人+猪), 信区: COM_DCOM
    标  题: ATL接口映射宏详解--(1)
    发信站: 武汉白云黄鹤站 (Fri Mar 31 05:35:56 2000), 站内信件

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

    文中所涉及的代码都为略写,只列出相关部分。


    一、COM_INTERFACE_ENTRY(x)
    首先我们从一个最典型的应用开始:
    定义一个最简单的ATL DLL:
    class ATL_NO_VTABLE CMyObject :
            public CComObjectRootEx,
            public CComCoClass,
            public IDispatchImpl
    {
    .....
    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 >::CreateInstance(...)
    2.ATL::CComCreator2 >,
        ATL::CComCreator > >::CreateInstance(...)
    1.ATL::CComClassFactory::CreateInstance(...)
    4.ATL::AtlModuleGetClassObject(...)
    9.ATL::AtlInternalQueryInterface(...)
    8.ATL::CComObjectRootBase::InternalQueryInterface(...)
    7.ATL::CComClassFactory::_InternalQueryInterface(...)
    6.ATL::CComObjectCached::QueryInterface(...)
    5.ATL::CComCreator >::
               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 /*lp
    Reserved*/)
      {
         .....
         _Module.Init(ObjectMap, hInstance, &LIBID_TEST2Lib);
         .....
      }初始化_Module //注意在有的情况下是在InitInstance()中初始化_Module

      那么_Module初始化都做了些什么呢,其实他什么也没做,在CComModule::Ini
    t中,它
      调用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(CComClassFact
    ory)
      #define DECLARE_CLASSFACTORY_EX(cf)
        typedef CComCreator< CComObjectCached< cf > > _ClassFactoryCreator
    Class;
      CComCreator,CComObjectCached我们暂且不管,但一看到CComClassFactory,
    顾名思
      义,我们就知道我们要的类厂终于出现了!每个组件内部原来都有一个类厂对
    象。
      绕了一大圈,我们现在已经知道了_Module中包含了我们所要的每个组件的类厂
    对象,
      这对目前来说已经足够了,现在继续路由下去!
    3:HRESULT CComModule::GetClassObject(REFCLSID rclsid,REFIID riid,LPVOI
    D* ppv)
      {
            return AtlModuleGetClassObject(this, rclsid, riid, ppv);
      }
      CComModule::GetClassObject的实现非常简单,仅仅是调用ATL的API函数。
    4:ATLINLINE ATLAPI AtlModuleGetClassObject(_ATL_MODULE* pM, REFCLSID r
    clsid,

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

         while (pEntry->pclsid != NULL)
         {  
            if ((pEntry->pfnGetClassObject != NULL) && InlineIsEqualGUID(r
    clsid,
                               *pEntry->pclsid))
            {
              if (pEntry->pCF == NULL)
              {
                 if (pEntry->pCF == NULL)
                    hRes = pEntry->pfnGetClassObject(pEntry->pfnCreateInst
    ance,
                                        IID_IUnknown, (LPVOID*)&pEntry->pC
    F);
              }
              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我们也可以猜出它是指向这个类厂的IUnk
    nown指
      针,代表这个类厂对象是否被创建过,若类厂对象已经存在,就不用再创建新
    的类厂
      对象了。现在就剩下pfnCreateInstance我们还不明白怎么回事。其实答案还是

      CComCoClass中!
      在CComCoClass中缺省定义了宏DECLARE_AGGREGATABLE(x),这个宏表示这个组件
    既可以
      是聚集的也可以是非聚集的,关于聚集的概念我们暂且不理,先看它的定义:

      #define DECLARE_AGGREGATABLE(x) public:\
            typedef CComCreator2< CComCreator< CComObject< x > >, \
                   CComCreator< CComAggObject< x > > > _CreatorClass;
      我们看到了一个熟悉的字符串_CreatorClass, 原来这还有一个组件包含的对象
    。但还
      有一个问题我们没有搞清楚,就是为什么_ClassFactoryCreator和_CreatorCl
    ass后面
      都要跟着一个CreateInstance? 看来我们必须先来看看CComCreator是个什么东
    西了。
      template 
      class CComCreator
      {
      public:
            static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LP
    VOID* ppv)
            {.....
            }
      };
      原来它里面只有一个CreateInstance函数,我们现在终于大体明白_ClassFact
    oryCre
      atorClass::CreateInstance表示什么意思了,它就代表CComClassFactory::C
    reateIn
      stance(..)吧,差不多就是这样了。那我们再来看看CComCreator2有什么不同

      template 
      class CComCreator2
      {
      public:
            static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LP
    VOID* ppv)
            {
                return (pv == NULL) ?
                            T1::CreateInstance(NULL, riid, ppv) :
                            T2::CreateInstance(pv, riid, ppv);
            }
      };
      这个类与CComCreator很类似,都只有一个CreateInstance成员函数,从_Creat
    orClass
      中我们可以知道它实际上包含两个类CComObject,CComAggObject的CreateInst
    ance函
      数(通过CComCreator),其中CComObject用于非聚集对象,CComAggObject用于聚
    集对象
      根据情况它建立相应的对象。(ATL中实际生成的组件对象不是CMyObject,而是

      CComObject,CComAggObject或CComPolyObject对象,这个概念很重要,但现在暂
    且不谈)
      现在我们对AtlModuleGetClassObject(...)基本已经知道是怎么回事了,它就
    是根据
      存在对象映射数组中的创建类厂的函数的地址来创建类厂。pfnGetClassObjec
    t以及
      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,这是我们给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宏,所以现在调用的是CComClassFactor
    y的。
      注意把this指针和接口映射数组_GetEntries()传给了InternalQueryInterfac
    e(),
      这是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** ppvObjec
    t)
      {
       ...
       HRESULT hRes = AtlInternalQueryInterface(pThis, pEntries, iid, ppvO
    bject);
       ...  
      }
      现在调用的是CComObjectRootBase::InternalQueryInterface(...)
    9:现在我们终于到了QueryInterface的鼻祖了。AtlInternalQueryInterface(..
    .)是整
      个查询过程的终点,它遍历接口映射表,并根据每一项做出相应的动作。ATL中
    的消
      息映射宏有很多种,相应的动作也很多,但现在我们不管那些,现在我们要做
    的就是
      查到一个IUnknown接口,这很容易,我们甚至不需要遍历接口映射表。
      ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis,
            const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void** ppvObjec
    t)
      {
            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是什么意思,我们以后再说吧,那也
    是一堆
      问题。总之,我们现在如愿以偿轻松的获得了我们所需要的类厂对象以及IUnk
    nown指
      针。
    4:我差一点以为我们可以胜得返回到第一步了,但在ATL::AtlModuleGetClassOb
    ject
      处却又停了下来,看看它的源码,原来还要再通过我们刚获得的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::QueryInterface(...)
    1.pUnk->QueryInterface(IID_IMyObject, (void **)&pMyObject);(客户端)


    解释如下:
    1.我们通过刚刚获得的组件对象的IUnknown接口指针来查询IMyObject指针,这才
    是我们
      真正需要的指针。
    2.还记得我们说过ATL真正创建的组件并不是CMyObject,而是CComObject,CComAg
    gObject
      或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** ppvObjec
    t)
      {
        //确保接口映射的第一项是个简单接口
        //若是查询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,pEntrie
    s->dw);
                  if (hRes == S_OK || (!bBlind && FAILED(hRes)))
                    return hRes;
               }
            }
            pEntries++;
         }
         return E_NOINTERFACE;
      }
      函数的逻辑很清楚,只有两点可能不太理解,一个是
      (IUnknown*)((int)pThis+pEntries->dw)是什么意思,另一个是pEntries->pF
    unc到底
      要干些什么事。前一个问题将在讲述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.依次退栈返回。
      其实这次查询发生的过程在刚才的调用序列中也发生了,当查询IClassFactor
    y接口时就
      有类似的过程,但还是把它单独提了出来,只为了看看典型的情形,呵呵。


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

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



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

    ※ 修改:.lostall 于 Mar 31 06:03:02 修改本文.[FROM: 202.114.1.166]
    ※ 来源:.武汉白云黄鹤站 bbs.whnet.edu.cn.[FROM: 202.114.1.166]

    --
    ...我是在黑夜里展翅飞翔的恐怖...

  • 相关阅读:
    启动matlab时总是直接跳转到安装界面的解决方案
    毕业倒计时
    PARAMETER和ARGUMENT的区别
    在Windows上使用Ubuntu共享的打印机
    Ubuntu 16.04 + CUDA 8.0 + cuDNN v5.1 + TensorFlow(GPU support)安装配置详解
    深度学习硬件购买指南
    GeForce GTX 1080 ti安装记录
    What is a TensorFlow Session?
    来来来,干了这碗毒鸡汤……
    [译] 理解 LSTM(Long Short-Term Memory, LSTM) 网络
  • 原文地址:https://www.cnblogs.com/cutepig/p/1554742.html
Copyright © 2020-2023  润新知