以文本方式查看主题 - 计算机科学论坛 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- ATL接口映射宏详解 [分享] (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=50812) |
-- 作者:一分之千 -- 发布时间:8/1/2007 11:22:00 AM -- ATL接口映射宏详解 [分享] ATL接口映射宏详解 (上) 序言: 这几天看了看ATL的接口映射宏,不知不觉看得比较深入了,突然就萌发了把它写出来的想法。ATL中定义了很多接口映射宏,有几个还是比较重要的,虽然好象没有必要把它所有的细节都弄得很清楚,但深入学习的过程中也可以顺带学一学其他的ATL类,对它的机制也可以更清楚一些,应该还是会有些好处的吧。我按照我学习的过程把它写出来,也 不知道大家能不能看懂。想模仿一下侯老师的手笔力争把其内部细节解释清楚,但也不敢大言不惭的美其名曰“深入浅出”,呵呵,只希望能对大家有所帮助了。 以后将分别介绍ATL中各个形式为COM_INTERFACE_ENTRY_XX的接口映射宏并将按照从易到难的顺序讲解,每一部分都将建立在前一部分的基础上。每一部分都将通过分析实际的调用函数堆栈来进行分析,堆栈的写法是从下向上。文中所涉及的代码都为略写,只列出相关部分。 一、COM_INTERFACE_ENTRY(x) 首先我们从一个最典型的应用开始: 定义一个最简单的ATL DLL: class ATL_NO_VTABLE CMyObject : 编写一段最简单的查询接口代码: IUnknown *pUnk; 执行客户代码,首先我们看看组件对象是如何被创建的。 4........... 解释如下: 2: 其中值得注意的是_Module变量,在DLL中定义了全局变量: BEGIN_OBJECT_MAP(ObjectMap) #define BEGIN_OBJECT_MAP(x) static _ATL_OBJMAP_ENTRY x[] = { 生成一个静态全局_ATL_OBJMAP_ENTRY型数组:ObjectMap[]; { 中初始化_Module //注意在有的情况下是在InitInstance()中初始化_Module CComCreator,CComObjectCached我们暂且不管,但一看到CComClassFactory,顾名思义,我们就知道我们要的类厂终于出现了!每个组件内部原来都有一个类厂对象。绕了一大圈,我们现在已经知道了_Module中包含了我们所要的每个组件的类厂对象,这对目前来说已经足够了,现在继续路由下去! 3: CComModule::GetClassObject的实现非常简单,仅仅是调用ATL的API函数。 4: while (pEntry->pclsid != NULL) 现在好象已经有点看不懂了,看来我们得看看_ATL_OBJMAP_ENTRY的结构了 pclsid很清楚就代表着我们组件的CLSID;pfnGetClassObject我们也已经知道了它就是CMyObject::_ClassFactoryCreatorClass::CreateInstance(我们组件所包含的类厂对象的CreateInstance函数);pCF我们也可以猜出它是指向这个类厂的IUnknown指针,代表这个类厂对象是否被创建过,若类厂对象已经存在,就不用再创建新的类厂对象了。现在就剩下pfnCreateInstance我们还不明白怎么回事。其实答案还是在 CComCoClass中! #define DECLARE_AGGREGATABLE(x) public:\ 我们看到了一个熟悉的字符串_CreatorClass, 原来这还有一个组件包含的对象。但还有一个问题我们没有搞清楚,就是为什么_ClassFactoryCreator和_CreatorClass后面都要跟着一个CreateInstance? 看来我们必须先来看看CComCreator是个什么东西了。 原来它里面只有一个CreateInstance函数,我们现在终于大体明白_ClassFactoryCreatorClass::CreateInstance 表示什么意思了,它就代表CComClassFactory::CreateInstance(..)吧,差不多就是这样了。那我们再来看看CComCreator2有什么不同: 这个类与CComCreator很类似,都只有一个CreateInstance成员函数,从_CreatorClass 中我们可以知道它实际上包含两个类CComObject,CComAggObject的CreateInstance函数(通过CComCreator),其中CComObject用于非聚集对象,CComAggObject用于聚集对象根据情况它建立相应的对象。(ATL中实际生成的组件对象不是CMyObject,而是 CComObject,CComAggObject或CComPolyObject对象,这个概念很重要,但现在暂且不谈) 现在我们对AtlModuleGetClassObject(...)基本已经知道是怎么回事了,它就是根据存在对象映射数组中的创建类厂的函数的地址来创建类厂。pfnGetClassObject以及 pfnCreateInstance我们基本上都已经知道是怎么回事了,但还有一个问题为什么要把pEntry->pfnCreateInstance作为pEntry->pfnGetClassObject(...)中的一个参数传递?答案在下面呢,让我们继续路由下去! 5: 注意这里的T1是CComObjectCached< ATL::CComClassFactory >,这是我们给CComCreator 的模板参数。我们又一次看到了我们熟悉的操作符'new'!直到现在我们终于创建了组件的类厂。但还没完,继续往下走,看看SetVoid(pv)里干了些什么? 大家还记得我们曾经把CMyObject::_CreatorClass::CreateInstance作为参数传给 pEntry->pfnGetClassObject(...)吧,当时我们不明白是怎么回事,现在已经豁然开朗!原来是类厂需要它来创建组件对象!虽然我们只是从字面意思猜出这一点,但实际上也正如我们所预料的那样,在CComClassFactory::CreateInstance(...)中,我们看到了m_pfnCreateInstance(pUnkOuter, riid, ppvObj);现在一切都已经明白了, ATL为我们创建类厂而作的层层包装我们都已经打开,剩下的创建组件的过程已经是我们很熟悉的过程了! 6: 现在调用的是CComObjectCached::QueryInterface,至于这个类有何特别之处,我们现在好象还不需要知道,我也很累的说,呵呵。 7: 所有的类的_InternalQueryInterface(...)都是在BEGIN_COM_MAP中定义的。 CComObjectCached没有BEGIN_COM_MAP宏,所以现在调用的是CComClassFactory的。注意把this指针和接口映射数组_GetEntries()传给了InternalQueryInterface(), 这是InternalQueryInterface(...)实现查询的依据。在BEGIN_COM_MAP(x)中定义了一个静态的接口映射数组: 每一个接口映射宏实际上都是向这个数组中增加了一项。一个接口映射宏包括三个部分:接口的IID号、偏移值(大部分时候下)、需要执行的函数,对一般接口来说不用执行其他函数。_GetEntries()就是返回这个数组。还有一些细节问题以后再说。 static HRESULT WINAPI InternalQueryInterface(void* pThis, 现在调用的是CComObjectRootBase::InternalQueryInterface(...) 9: *ppvObject = NULL; 这里有一个规定,接口映射表的第一个接口必须是_ATL_SIMPLEENTRY型的。至于为什么有这个要求,以及pThis+pEntries->dw是什么意思,我们以后再说吧,那也是一堆问题。总之,我们现在如愿以偿轻松的获得了我们所需要的类厂对象以及IUnknown指针。 1:终于终于我们已经完成了创建类厂对象的全部操作,现在我们要做的就是我们熟悉的调用类厂对象的CreateInstance(...)函数创建组件的过程了。正如我们所见到的,现在OLE开始调用CComClassFactory::CreateInstance()了,我们还没忘记,在类厂对象中保留了创建组件用的CreateInstance()函数, 这个过程已经很明朗了。 2.不用再重复了吧,看第4步。 3.不用再重复了吧,看第4步。 4.如果继续路由下去的话,我们的堆栈还可以很长,但这只是重复的枯躁的劳动。我就不继续走下去了,我也很累的说,唉。 函数调用堆栈二: 0:............ 解释如下: 2.还记得我们说过ATL真正创建的组件并不是CMyObject,而是CComObject,CComAggObject 或CComPolyObject,这里我们创建的是CComObject.所以理所当然我们要调用 CComObject::QueryInterface(...),而确实CComObject也实现了这个函数。 STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) 4.这个调用我们也很熟悉了,不用多说了吧 5.现在我们将要查询的是一个非IUnknown接口,所以我们来看看我们以前没列出的代码 ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis, 函数的逻辑很清楚,只有两点可能不太理解,一个是 (IUnknown*)((int)pThis+pEntries->dw)是什么意思,另一个是pEntries->pFunc到底 要干些什么事。前一个问题将在讲述COM_INTERFACE_ENTRY2中讲述,后一个问题将在以后讲述不同类型的接口时分别解释。饭总是要一口一口吃的嘛,呵呵。 {&_ATL_IIDOF(IMyObject), //得到IMyObject的IID值 同样对于offsetofclass(IMyObject, CMyObject)我们也将留到下一次再讲。根据这个结构,我们很容易就能获得IMyObject接口指针。 二、COM_INTERFACE_ENTRY2(x, x2) 参ATL例程:COMMAP ATL中是以多重继承的方式来实现组件的,但在继承树中如果有多个分支实现了同一个接口,当查询这个接口时就需要知道把哪个分支返回给它。这个宏就是干这个工作的通常这个宏是用于IDispatch接口。我们先来看看它的典型用法: class COuter : IDispatchImpl<...>这个类中实现了IDispatch接口,所以现在组件中有两个IDispatch 的实现。那查询IDispatch接口时,返回哪个实现呢? #define BEGIN_COM_MAP(x) public: \ 现在问题就在于(DWORD)((x*)(x2*)((_ComMapClass*)8))-8是个什么意思? class A1 class A2 : public A1 class A3 : public A1 class A : public A2, public A3 { 这个继承图是个典型的菱形结构,在类A中保存有两个虚函数表指针,分别代表着它的两个分支。当为类A申明一个对象并实例化时,系统会为其分配内存。在这块内存的最顶端保留着它的两个虚函数表指针。分析程序运行的结果,可以看出,最后的结果4代表了指向接口A3的虚函数表指针与类A对象的内存块顶端之间的偏移量。 class B1 class B2 class B3 class B4 : public B1, public B2 class B5 : public B2, public B3 class B : public B4, public B5 { 类B将保留四个虚函数表指针,因为它共有四个分支。我们的目的是想获得B::B5::B2这个分支中的B2接口,最后的结果8正是我们所需要的,它表示在类B内存块的偏移量。 函数堆栈: 5.ATL::AtlInternalQueryInterface(...) 解释: 1:这是我们的验证代码,pUnk是组件的IUnknown指针 2--5:这些代码我们现在都已经很熟悉了,我们只需再看看AtlInternalQueryInterface 的具体实现。 ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis, 关键的一句话就是IUnknown* pUnk = (IUnknown*)((int)pThis+pEntries->dw); |
-- 作者:一分之千 -- 发布时间:8/1/2007 11:22:00 AM -- ATL接口映射宏详解 (中) 三、COM_INTERFACE_ENTRY_TEAR_OFF(iid, x) 参考ATL例程Beeper、COMMAP 使用这个宏的目的就是为了把一些很少用到的接口放在一个单独的组件中实现,仅当查询到这个接口时,才创建这个组件,并且当它的引用计数减为0时就会被释放掉。我们知道ATL中组件是通过多重继承实现的,每继承一个接口,在为它分配的内存块中就会多一个虚函数表指针,用这个宏就可以为每个组件的实例节省下这一个虚函数表指针来(一个指针4个字节,好象也不多啊,呵呵) BEGIN_COM_MAP(CTearOff1) HRESULT STDMETHODCALLTYPE get_Name(BSTR* pbstrName) class COuter : public ..... //我们真正要实现的组件 CTearOff1实现了Tear-off接口ITearOff1,实现方法与其他组件并无不同。唯一不同的是它从CComTearOffObjectBase继承,CComTearOffObjectBase定义如下: template < class Owner, class ThreadModel = CComObjectThreadModel > 我们又看到了我们熟悉的一个类CComObject,它是组件的真正生成类。从上面的定义中可知道CComTearOffObjectBase主要功能就是包含了一个指向外部对象(在这里就是我们的组件类CComObject)的指针。它的功能将在后面看到。 7.CTearOff1::_InternalQueryInterface(...) 解释: 1--4:这些代码已经遇到过很多次了,我们还是集中精力看看核心代码: ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis, 当在COuter的接口映射数组中找到ITearOff1后,因为它不是一个简单接口,所以要执行pEntries->pFunc(....)。 #define COM_INTERFACE_ENTRY_TEAR_OFF(iid, x)\ 看不太明白,还是继续我们路由得了 static HRESULT WINAPI _Creator(void* pv, REFIID iid, void** ppvObject,DWORD) struct _ATL_CREATORDATA typedef HRESULT (WINAPI _ATL_CREATORFUNC)(void* pv, REFIID riid, LPVOID* ppv); template < class Creator > 源代码都列出来了,不用我多说,大家也都能看懂了。继续路由吧 template < class T1 > 同我们所见到的大多数Creator类一样,它也只有一个静态CreateInstance函数。现在我们终于可以创建我们分割组件了,它不是CTearOff1,它也是经了一层包装的,是 CComTearOffObject! 现在我们再来看看它的构造函数干了些什么事: 还记得CTearOff1是从CComTearOffObjectBase继承的吗,这个基类包含了一个成员变量m_pOwner,现在它被赋值为指向它的外部对象的指针了。 执行pTear1->QueryInterface(ITearOff1, (void **)&pTear2) 4.............. 解释: 1: STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) 还记得我们创建的分割组件是CComTearOffObject< CTearOff1 >吗?现在执行查询操作的是它的成员函数。它的实现很简单,事实上它什么也没做,仅仅是把它交给它的外部对象(即CComObject< COuter >)去做了。还记得m_pOwner是在构造函数里赋值的吧。现在是否感到有些不妙呢?呵呵 四.COM_INTERFACE_ENTRY_CACHED_TEAR_OFF(iid, x, punk) 参ATL例程COMMAP 这个宏与上一节所讲的COM_INTERFACE_ENTRY_TEAR_OFF宏最主要的不同就在于,当查询分割对象中其他接口时,不会再新建新的对象。下面还是先看看它的典型用法: BEGIN_COM_MAP(CTearOff2) HRESULT STDMETHODCALLTYPE get_Name(BSTR* pbstrName) class COuter : public .... CComPtr< IUnknown > m_pUnkTearOff2; CTearOff2实现了分割接口ITearOff2,它的类定义与上一节所看见的CTearOff1一模一样可见不管是哪种分割接口,实现都是一样的,不同的地方在于COuter。在COuter中增加了一个成员变量m_pUnkTearOff2作为宏的一个参数。 执行pOuter->QueryInterface(IID_ITearOff2, (void **)&pTear1); 函数堆栈一: 9.CTearOff2::_InternalQueryInterface(...) 解释: 1:pOuter->QueryInterface(IID_ITearOff2, (void **)&pTear1); 2-5:这段代码见到很多次了,不用再讲了,现在程序执行到 HRESULT hRes = pEntries->pFunc(pThis, iid, ppvObject, pEntries->dw); 与我们上一节见的宏的定义不太一样,还是先跟踪下去再说。 static HRESULT WINAPI _Cache(void* pv,REFIID iid,void** ppvObject,DWORD dw )\ static HRESULT WINAPI _Cache(void* pv,REFIID iid,void** ppvObject,DWORD dw) 现在问题的关键是dw了,dw是从pEntries->dw传过来的。我们得看一下宏定义中的 是什么意思。 CComCreator我们在前面已经见过它的定义了,它只有一个成员函数CreateInstance. CComContainedObject< contained > m_contained; CComCachedTearOffObject是这个宏与上一节所讲宏不同的关键所在,因为它包含了一个CComContainedObject的对象。这个对象的作用在查询的时候再讲。 typedef HRESULT (WINAPI _ATL_CREATORFUNC)(void* pv, REFIID riid, LPVOID* ppv); 要注意的是从_Cached()函数传进来的参数dw就是_ATL_CACHEDATA结构的变量,所以可知道 CComCachedTearOffObject(void* pv) 这里contained就是CTearOff2,contained::_OwnerClass就是COuter,可见m_contained保存了外部对象的指针。 STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) 注意,这里把查询工作交给了m_contained,也就是一个CComContainedObject对象。不过现在查询的是IUnknown指针,别忘了,我们在COuter中还定义了一个IUnknown指针呢,现在查询的就是它!! 9:因为CComContainedObject m_contained的基类是CTearOff2,所以将调用CTearOff2::_InternalQueryInterface(...) 剩下的操作就没什么特别之处了,仅仅一般的查询操作。 执行pTear1->QueryInterface(ITearOff2, (void **)&pTear2); 函数堆栈二: 12.ATL::AtlInternalQueryInterface(...) 解释: 1:第一步就可能使我们迷惑了,为什么执行的是CComContainedObject::QueryInterface template < class contained > 原来CComCachedTearOffObject没有从contained类(在这里就是CTearOff2)中继承,而 CComTearOffObject却是从CTearOff1继承的!所以我们刚才得到的pTear1就不可能是CComCachedTearOffObject的对象。而实际上,CComContainedObject是从CTearOff2继承的,在上面的函数堆栈中第9步查询ITearOff2接口时,把工作交给了m_contained, 这是个CComContainedObject< CTearOff2 >对象,所以实际上最后查询得到的ITearOff2 指向的是CComContainedObject< CTearOff2 >对象。所以现在执行的会是 CComContainedObject::QueryInterface(...)!!! HRESULT OuterQueryInterface(REFIID iid, void ** ppvObject) 把查询工作交给外部对象完成,也就是COuter。 static HRESULT WINAPI _Cache(void* pv, REFIID iid, void** ppvObject,DWORD dw) 还记得我们在COuter中定义了一个IUnknown指针m_pUnkTearOff2吧,我们在第一次查询 ITearOff2接口时,创建了CTearOff2对象,并查询一个IUnknown指针给了它.现在它就发挥作用了,在_Cache中将判断如果m_pUnkTearOff2不等于空,则表明CTearOff2已经创建就不会再创建它了,而是直接用它去查询接口. 10-12:以下的工作就很简单了,不再赘述。 总结: COM_INTERFACE_ENTRY_CACHED_TEAR_OFF是个相对比较麻烦的宏,它与上一节介绍的宏相比不同之处就在于创建分割接口对象的过程只用进行一次,如果对象已经创建,则下一次查询该对象的接口时不会再创建一个新的分割对象。为了达到这个目的,它在外部对象中包含了一个IUnknown指针,并在第一次创建分割对象时查询这个IUnknown指针,这样就可以通过判断这个指针是否为空来知道这个分割对象是否已经创建,从而决定是否创建新的分割对象,并通过它去查询分割对象内其它接口。这里特别需要注意的是,实际上有两个对象被创建,一个是CComCachedTearOffObject< CTearOff2 >,另一个是 CComContainedObject< CTearOff2 >。并且第一个对象内部实现了第二个对象,真正的查询工作也是交给第二个对象去做。COuter::m_pUnkTearOff2是前面一个对象的IUnknown指针,当用它去查询ITearOff2时,实际上是交给了其内部对象m_contained去做了,这在第8、9步可以看得很清楚。 |
-- 作者:一分之千 -- 发布时间:8/1/2007 11:23:00 AM -- ATL接口映射宏详解(下) 五.COM_INTERFACE_ENTRY_AGGREGATE(iid, punk) 参ATL例程COMMAP 这一节中将介绍ATL中用于聚集对象的宏。聚集对象的概念请参阅其它参考书。 CAgg是一个聚集类,它的实现与一般的ATL组件没有区别,只是注意在它的类定义中不要加入DECLARE_NO_AGGREGATABLE. BEGIN_COM_MAP(COuter) DECLARE_GET_CONTROLLING_UNKNOWN() CComPtr< IUnknown > m_pUnkAgg; COuter包含了聚合组件CAgg,它包含了几个不同之处: #define COM_INTERFACE_ENTRY_AGGREGATE(iid, punk)\ offsetof我们在上一节中已经见过,可以猜到它求的就是punk在类中的位置。也就是m_pUnkAgg在COuter中的位置。 #define DECLARE_GET_CONTROLLING_UNKNOWN() public:\ 我们也没必要继续深究下去,仅从字面意思就可以看出这个函数将返回组件的IUnknown 指针。 (4)重载了FinalConstruct,FinalRelease HRESULT COuter::FinalConstruct() void COuter::FinalRelease() 当创建组件COuter后将会调用FinalConstruct,所以会在这里创建聚集组件。原则上聚集组件可以仅在需要的时候才创建,但也可以随着包含它的组件一起创建。聚集组件的创建没什么特别之处,只是要注意它将查询IUnknown指针,并返回给m_pUnkAgg.外部组件将通过m_pUnkAgg操作聚集组件。另外注意到使用pUnkOuter作为CoCreateInstance的参数,这将导致创建CComAggObject< COuter >对象,内部包含一个CComContainedObject的包含对象。与上一节中的CComCachedTearOff<>类似,CComAggObject< COuter >也不是从COuter派生的,所以真正的组件对象不是CComAggObject< COuter >对象,而是它内部包含的CComContainedObject< COuter >对象。同样pUnkOuter得到的将是CComAggObject<>的IUnknown指针,也同样调用它的QueryInterface会转而调用CComContainedObject的_InternalQueryInterface函数(呵呵,现在可都还是我猜的,看我猜的对不对吧) 函数堆栈一: 9.ATL::AtlInternalQueryInterface(...) 1-5:这几步函数调用我们已经见了很多次了,因为在这个宏定义使用了_Delegate,所以将调用CComObjectRootBase::_Delegate(...). static HRESULT _Delegate(void* pv,REFIID iid,void** ppvObject,DWORD dw) STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) 也正如我们所料,将交给它的包含对象去做.(这段代码在上一节好象也见过是吧,呵呵) 运行pAgg1->QueryInterface(IID_IAgg, (void **)&pAgg2) 函数堆栈二: 9.CAgg::_InternalQueryInterface(...) 解释: 1-9:浏览整个堆栈,与我们上一节所见的堆栈二太相近了,这是因为都是使用了包含对象。包含对象起了个代理的作用,他先把查询交给外部对象(COuter)去做(第1,2步), 当外部对象发现要查询的是聚集组件的接口时(IAgg),就会再把查询交还给它保留的聚集组件的指针(m_pUnkAgg,第7步中,注意这不是真正的聚集组件),m_pUnkAgg再把查询交给包含对象(第8步中),包含对象再把查询交给真正实现接口的类CAgg(第9步). 若外部对象发现要查询的是外部组件的接口时,那就很简单了,直接查询就行了。这样就防止了外部组件与聚集组件查询操作的不一致性。唉,真个过程真麻烦,不过还好,与上一节的宏很类似。相关的源码可参看上一节。 六、COM_INTERFACE_ENTRY_AGGREGATE_BLIND 参ATL例程COMMAP 上一节我们讲了COM_INTERFACE_ENTRY_AGGREGATE,这节要介绍的宏与它很类似。 #define COM_INTERFACE_ENTRY_AGGREGATE_BLIND(punk)\ 从定义上就可以看出,它与上一节介绍宏的唯一区别就在于,它没有指明接口ID!! 注意变量bBlind: BOOL bBlind = (pEntries->piid == NULL); BEGIN_COM_MAP 当查询IOuter接口时就会出错!!! 先看看这个宏的定义: #define COM_INTERFACE_ENTRY_AUTOAGGREGATE(iid, punk, clsid)\ 先看看它的典型用法: 与一般的组件并无二样。 CComPtr m_pUnkAutoAgg; 与宏COM_INTERFACE_ENTRY_AGGREGRATE(_)不同,COuter不用在FinalConstruct中创建聚集组件。外部组件会自动创建聚集组件!!! template < class Creator, DWORD dwVar > 2。 static HRESULT WINAPI _Cache(void* pv, REFIID iid, void** ppvObject, DWORD dw) 3。 template < class T, const CLSID* pclsid > 因为_Cache,_CComCacheData,CComAggregateCreator这几个类和函数我们已经在前面见过或者见过类似的,所以就不再多讲了。总之我们可以看到,若m_pUnkAutoAgg.p不为空则直接查询,否则创建聚集组件。 看看它的定义: #define COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND(punk, clsid)\ 先看看它的定义: #define COM_INTERFACE_ENTRY_CHAIN(classname)\ 典型用法: class CChain : 它与一般的组件无异。 我们对查询的过程已经很熟悉了,可以直接来看看_Chain的功能。_Chain()是CComObjectRootBase的成员函数: struct _ATL_CHAINDATA 我们再看看宏定义中的dw部分: 基本上我们已经看懂是怎么回事了,void *p将得到基类的指针,InteralQueryInterface 我们已经很熟悉了,_Chain把基类的指针以及基类的接口映射宏传给它,实际上是查询基类的接口!!! 所有重要的宏我们都已经讲过了,剩下的都是些很简单的宏了.呵呵,还是把它们都罗列一下,善始善终嘛. 十、COM_INTERFACE_ENTRY_IID(iid, x) #define COM_INTERFACE_ENTRY_IID(iid, x)\ 十一、COM_INTERFACE_ENTRY2_IID(iid, x, x2) #define COM_INTERFACE_ENTRY2_IID(iid, x, x2)\ 从定义上看这两个宏与COM_INTERFACE_ENTRY()和COM_INTERFACE_ENTRY2()相比,都只是多了一项"iid"。没有别的好处,只不过由用户明确指出接口IID,而不用系统根据接口名字去转换了。 #define COM_INTERFACE_ENTRY_FUNC(iid, dw, func)\ 还记得AtlInternalQueryInterface()中的代码吗?如果在接口映射表中找到了我们要找的接口,并且这个接口不是_ATL_SIMPLEENTRY型的,则执行宏定义中的指定的函数。 #define COM_INTERFACE_ENTRY_FUNC_BLIND(dw, func){NULL, \ 至于_BLIND类型的特点可以看前面几节。 #define COM_INTERFACE_ENTRY_NOINTERFACE(x){&_ATL_IIDOF(x), NULL, _NoInterface}, _NoInterface是CComObjectRootBase的成员函数,看看它的定义: 原来它只是返回E_NOINTERFACE,并且将终止查询。 #define COM_INTERFACE_ENTRY_BREAK(x){&_ATL_IIDOF(x), NULL, _Break}, _Break也是CComObjectRootBase的成员函数,看看它的定义: 如果查到这个接口将调用DebugBreak(),并返回S_FALSE,继续查询下去。DebugBreak()是什么效果大家自己试试吧,一定很熟悉的,呵呵。 2000/3/31凌晨 |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
128.906ms |