




已阅读5页,还剩31页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
ATL接口映射宏详解序言: 这几天看了看ATL的接口映射宏,不知不觉看得比较深入了,突然就萌发了把它写出来的想法。ATL中定义了很多接口映射宏,有几个还是比较重要的,虽然好象没有必要把它所有的细节都弄得很清楚,但深入学习的过程中也可以顺带学一学其他的ATL类,对它的机制也可以更清楚一些,应该还是会有些好处的吧。我按照我学习的过程把它写出来,也 不知道大家能不能看懂。想模仿一下侯老师的手笔力争把其内部细节解释清楚,但也不敢大言不惭的美其名曰“深入浅出”,呵呵,只希望能对大家有所帮助了。 以后将分别介绍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 ATL:CComObject :CreateInstance(.) 2.ATL:CComCreator2 ATL:CComCreator ATL:CComObject , ATL:CComCreator ATL:CComAggObject :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则会通过 Load Library(.)装入DLL,并调用DLL中的DllGetClassObject()函数。2: STDAPI DllGetClassObject(REFCLSID closed, REFIID rid, LPVOID* pip)return _Module.GetClassObject(closed, rid, pip); 其中值得注意的是_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 OBJECT_ENTRY(clsid, class) &clsid, class:UpdateRegistry, class:_ClassFactoryCreatorClass:CreateInstance, /关键 class:_CreatorClass:CreateInstance, NULL, 0, class:GetObjectDescription, class:GetCategoryMap, class:ObjectMain , #define END_OBJECT_MAP() NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL; 生成一个静态全局_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 _ClassFactoryCreatorClass; CComCreator,CComObjectCached我们暂且不管,但一看到CComClassFactory,顾名思义,我们就知道我们要的类厂终于出现了!每个组件内部原来都有一个类厂对象。绕了一大圈,我们现在已经知道了_Module中包含了我们所要的每个组件的类厂对象,这对目前来说已经足够了,现在继续路由下去!3: HRESULT CComModule:GetClassObject(REFCLSID rclsid,REFIID riid,LPVOID* pip)return AtlModuleGetClassObject(this, closed, rid, pip); CComModule:GetClassObject的实现非常简单,仅仅是调用ATL的API函数。 4: ATLINLINE ATLAPI AtlModuleGetClassObject(_ATL_MODULE* pM, REFCLSID closed, REFIID rid, LPVOID* pip) _ATL_OBJMAP_ENTRY* pEntry = pM-m_pObjMap;/从_Module中取出对象映射数组 while (pEntry-pclsid != NULL) if (pEntry-pfnGetClassObject != NULL) & InlineIsEqualGUID(closed, *pEntry-pclsid) if (pEntry-pCF = NULL) hRes = pEntry-pfnGetClassObject(pEntry-pfnCreateInstance, IID_IUnknown, (LPVOID*)&pEntry-pCF); if (pEntry-pCF != NULL) hRes = pEntry-pCF-QueryInterface(rid, pip); 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 , CComCreator ccomaggobject _CreatorClass; 我们看到了一个熟悉的字符串_CreatorClass, 原来这还有一个组件包含的对象。但还有一个问题我们没有搞清楚,就是为什么_ClassFactoryCreator和_CreatorClass后面都要跟着一个CreateInstance? 看来我们必须先来看看CComCreator是个什么东西了。template class CComCreator public: static HRESULT WINAPI CreateInstance(void* pv, REFIID rid, LPVOID* pip) . ; 原来它里面只有一个CreateInstance函数,我们现在终于大体明白_ClassFactoryCreatorClass:CreateInstance 表示什么意思了,它就代表CComClassFactory:CreateInstance(.)吧,差不多就是这样了。那我们再来看看CComCreator2有什么不同: template class CComCreator2 public: static HRESULT WINAPI CreateInstance(void* pv, REFIID rid, LPVOID* pip) return (pv = NULL) ? T1:CreateInstance(NULL, rid, pip) : T2:CreateInstance(pv, rid, pip); ; 这个类与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 rid, LPVOID* pip) 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(rid, pip); 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, rid, 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: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_INTERFACE_ENTRY2(x, x2) ATL中是以多重继承的方式来实现组件的,但在继承树中如果有多个分支实现了同一个接口,当查询这个接口时就需要知道把哪个分支返回给它。这个宏就是干这个工作的通常这个宏是用于IDispatch接口。我们先来看看它的典型用法: class COuter : public IDispatchImpl,/IOuter1是一个双接口public IDispatchImpl,/IOuter2也是一个双接口public . public: COuter() . BEGIN_COM_MAP(COuter) COM_INTERFACE_ENTRY2(IDispatch, IOuter2) ,/将暴露IOuter2所继承的路线 , COM_INTERFACE_ENTRY(IOuter1) COM_INTERFACE_ENTRY(IOuter2) . END_COM_MAP; IDispatchImpl这个类中实现了IDispatch接口,所以现在组件中有两个IDispatch 的实现。那查询IDispatch接口时,返回哪个实现呢? 我们再来看看COM_INTERFACE_ENTRY2(x, x2)的定义 #define BEGIN_COM_MAP(x) public: typedef x _ComMapClass; . #define COM_INTERFACE_ENTRY2(x, x2) &_ATL_IIDOF(x), /得到接口的IID值 (DWORD)(x*)(x2*)(_ComMapClass*)8)-8, _ATL_SIMPLEMAPENTRY, /表明是一个简单接口 现在问题就在于(DWORD)(x*)(x2*)(_ComMapClass*)8)-8是个什么意思? 我们先来考察一下下面一段代码: class A1 public: virtual void Test() ; class A2 : public A1 public: virtual void Test() ; class A3 : public A1 public: virtual void Test() ; class A : public A2, public A3 ; DWORD dw; dw = (DWORD)(A *)8); /dw = 0x08 dw = (DWORD)(A3 *)(A *)8); /dw = 0x0c dw = (DWORD)(A1 *)(A3 *)(A *)8); /dw = 0x0c dw = (DWORD)(A1 *)(A3 *)(A *)8) - 8;/dw = 4 这个继承图是个典型的菱形结构,在类A中保存有两个虚函数表指针,分别代表着它的两个分支。当为类A申明一个对象并实例化时,系统会为其分配内存。在这块内存的最顶端保留着它的两个虚函数表指针。分析程序运行的结果,可以看出,最后的结果4代表了指向接口A3的虚函数表指针与类A对象的内存块顶端之间的偏移量。 下面我们再看一个更为复杂点的继承关系: class B1 public: virtual void Test() ; class B2 public: virtual void Test() ; class B3 public: public: virtual void Test() ; class B4 : public B1, public B2 public: virtual void Test() ; class B5 : public B2, public B3 public: virtual void Test() ; class B : public B4, public B5 ; DWORD dw; dw = (DWORD)(B *)8); /dw = 0x08 dw = (DWORD)(B5 *)(B *)8);/dw = 0x10 dw = (DWORD)(B2 *)(B5 *)(B *)8);/dw = 0x10 dw = (DWORD)(B2 *)(B5 *)(B *)8) - 8;/dw = 8 类B将保留四个虚函数表指针,因为它共有四个分支。我们的目的是想获得B:B5:B2这个分支中的B2接口,最后的结果8正是我们所需要的,它表示在类B内存块的偏移量。从上面两个例子中,我们已经明白了(DWORD)(x*)(x2*)(_ComMapClass*)8)-8的作用通过这个值我们能获得我们所需要的接口。 下面我们针对我们的实际情况COM_INTERFACE_ENTRY2(IDispatch, IOuter2)来分析一下IDispatchImpl模板类从类T中派生,所以COuter要从两个它的模板类中继承, IOuter1、IOuter2都是双接口,即都是从IDispatch派生的类,所以可得COuter有两条分支,也是个菱形结构,所以按照我们的示例,这个偏移值也应该是4。为了证明我们的设想,我们再来通过函数堆栈来验证我们的结果。 函数堆栈: 5.ATL:AtlInternalQueryInterface(.) 4.ATL:CComObjectRootBase:InternalQueryInterface(.) 3.CMyObject:_InternalQueryInterface(.) 2.ATL:CComObject:QueryInterface(.) 1.pUnk-QueryInterface(IID_IDispatch, (void *)&pDispatch) 解释: 1:这是我们的验证代码,pUnk是组件的IUnknown指针 2-5:这些代码我们现在都已经很熟悉了,我们只需再看看AtlInternalQueryInterface 的具体实现。 ATLINLINE ATLAPI AtlInternalQueryInterface(void* pThis, const _ATL_INTMAP_ENTRY* pEntries, REFIID iid, void* ppvObject) . while (pEntries-pFunc != NULL) BOOL bBlind = (pEntries-piid = NULL); if (bBlind | InlineIsEqualGUID(*(pEntries-piid), iid) if (pEntries-pFunc = _ATL_SIMPLEMAPENTRY) /offset ATLASSERT(!bBlind); IUnknown* pUnk = (IUnknown*)(int)pThis+pEntries-dw); pUnk-AddRef(); *ppvObject = pUnk; return S_OK; ./如果是非简单接口的话. pEntries+; return E_NOINTERFACE; 关键的一句话就是IUnknown* pUnk = (IUnknown*)(int)pThis+pEntries-dw); 通过观察变量,正如我们所料pEntries-dw=4。(int)pThis+pEntries-dw)保证了我们可以得到IOuter2分支的虚函数表,又因为IDispatch也是从IUnknown继承,在虚函数表的最顶端放的是IUnknown的虚函数指针,所以进行(IUnknown *)强制转换,可以获得这个虚函数表的顶端地址,这正是我们所需要的。或许会问为什么得到的是虚函数表的地址,而不是一个类实例的地址呢?别忘了,接口是没有数据的,它只有纯虚函数。对于客户来说,它只能通过接口定义的虚函数来访问它,而不可能访问实现接口的类的成员变量,组件的数据对客户来说是不可见的,所以只用得到虚函数表的地址就行了。三、COM_INTERFACE_ENTRY_TEAR_OFF(iid, x) 使用这个宏的目的就是为了把一些很少用到的接口放在一个单独的组件中实现,仅当查询到这个接口时,才创建这个组件,并且当它的引用计数减为0时就会被释放掉。我们知道ATL中组件是通过多重继承实现的,每继承一个接口,在为它分配的内存块中就会多一个虚函数表指针,用这个宏就可以为每个组件的实例节省下这一个虚函数表指针来(一个指针4个字节,好象也不多啊,呵呵)下面我们来看它的典型用法: class CTearOff1: /该类是专门用来实现分割接口ITearOff1的 public IDispatchImpl,public CComTearOffObjectBase /外部对象 public: CTearOff1() CTearOff1() BEGIN_COM_MAP(CTearOff1) COM_INT
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 广播台竞选课件
- Hexyl-4-hydroxybenzoate-Hexylparaben-生命科学试剂-MCE
- 2025内蒙古锡林郭勒盟锡林浩特市第二批公益性岗位人员招募139人考前自测高频考点模拟试题及1套参考答案详解
- 2025广西百色市平果市国有平果林场拟聘用编外人员考前自测高频考点模拟试题及答案详解(易错题)
- 无线充电行业市场竞争分析
- 动物保护协议书规范
- 数码娱乐行业数码娱乐产品推广策略研究
- 碎石承包运输合同书9篇
- 紧急预案响应与救援成果承诺书5篇
- 供应链管理流程模板物流与仓储整合
- 4.2《遵守规则》教学设计 -2025-2026学年八年级道德与法治上册
- 人工智能+高质量发展文化旅游产业智能化升级研究报告
- 2025年自考专业(计算机网络)考试综合练习附参考答案详解(A卷)
- 冷链技术对水果品质保持的数值预测模型研究
- 集输工应急处置考核试卷及答案
- 2025年全国保密教育线上培训考试试题库附完整答案(必刷)
- 珠江医院护理面试题库及答案
- 企业融资培训课件
- GB/T 3810.14-2016陶瓷砖试验方法第14部分:耐污染性的测定
- 大学信息系统建设与运行维护管理办法
- 大学植物学1细胞
评论
0/150
提交评论