接口的其他实现方法_第1页
接口的其他实现方法_第2页
接口的其他实现方法_第3页
接口的其他实现方法_第4页
接口的其他实现方法_第5页
已阅读5页,还剩45页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、1六 . COM接口的其他实现方法n基于表格驱动的接口查询n接口查询的本质n宏n应用n多重继承下的名字冲突n潜在的缺陷n临时的方案n使用复合技术(嵌套类)实现接口nCOM主对象和COM子对象nCOM 主对象的实现nCOM 子对象的实现n基于复合技术的COM对象的内存结构使用前面的方法,我们已经可以顺利地实现接口与对象了.这些基本的技术是实现COM的基石.但是,人类对于完美与高效的追求是无止境的.本章介绍一些更加精细的技术,看这些技巧是如何提高COM的效率,如何使得COM的功能细致入微的.n针对接口的引用计数n需求n实现方法n多重继承与复合的结合n复合技术的表格驱动n动态复合接口nMFC对COM

2、的支持21.基于表格驱动的接口查询n1.1 接口查询的本质接口查询的本质n前面我们使用继承的方式实现接口,使用多重继承的方式实现多个接口. 在这种方式下,接口的查询QueryInterface函数的实现非常的直接且直观. n在多重继承方式下,接口类是基类, IUnkown接口是最上层的基类. 对象类是接口类的派生的子类. 在内存中,子类比基类“大”. 因为子类除了包含基类的成员以外,还包含自己的成员.子类的一个实例中包含有基类的一个“subobject”,子对象. 如果这个基类还有基类,这个子对象中还含有一个更上层的子对象. nQueryInterface函数的本质是: 使用statice_c

3、ast操作符在子类的对象中加上基类的偏移偏移从而从而得到基类的子对象.转换到不同的基类时,要加上不同的偏移.n所以QueryInterface实际上是在不同的基类和不同的偏移中工作. 我们可以把基类和对应的偏移量抽象出来.作成表格.使得QueryInterface的工作更加形式化. 最终使得COM的编码更加形式化. (这里的工作为向MFC过渡而热身.我们看到,MFC中古怪的代码也是有其理性的由来的. )31.2 宏n为了实现表格驱动的QueryInterface, 我们定义这样的一个结构:ntypedef HRESULT (STDAPICALLTYPE *INTERFACE_FINDER)(v

4、oid *pThis, DWORD dwData, REFIID riid, void *ppv);/这是一个查询接口的函数,暂时这里并没有用上.n#define ENTRY_IS_OFFSET INTERFACE_FINDER(-1) /所以这里定义了一个伪函数ENTRY_IS_OFFSET ntypedef struct _INTERFACE_ENTRY const IID * pIID; / 要寻找的接口的IID INTERFACE_FINDER pfnFinder; / finder function long dwData; / finder function所需的参数.这里指偏移量

5、. INTERFACE_ENTRY; /暂时,这里只使用了第一和第三个第一和第三个分量4n然后定义了几个宏:n#define BASE_OFFSET(ClassName, BaseName) (DWORD(static_cast(reinterpret_cast(0 x10000000) - 0 x10000000)这个宏用来计算基类BaseName到子类ClassName的偏移.(一个完整对象中的基类子对象的起始地址到完整对象的起始地址的偏移)nreinterpret_cast(0 x10000000) 把绝对内存地址0 x10000000转换成为子类对象的地址.nstatic_cast(子

6、类指针) 把子类指针转换为基类指针.这个工作由编译器计算出偏移.并且加上偏移值.nDWORD(基类指针地址) - 0 x10000000 基类指针地址转换成绝对的数字后减去子类指针的绝对地址的数字.宏BASE_OFFSET(ClassName, BaseName) 返回基类BaseName到子类ClassName的偏移量. 如下图所示:1.在这个宏的基础上又定义了几个宏:0 x10000 x1012ClassName 子类BaseName 基类偏移5n#define BEGIN_INTERFACE_TABLE(ClassName) typedef ClassName _InterfaceTab

7、leClassName;/申明了一个静态函数静态函数,返回接口表返回接口表static const INTERFACE_ENTRY *GetInterfaceTable(void) static const INTERFACE_ENTRY table = /函数体中函数体中, 定义一个静态的接口表定义一个静态的接口表, 定义了类,此表就分配了.对所有的对象而言是同一个.n#define IMPLEMENTS_INTERFACE(ItfName) &IID_#ItfName, ENTRY_IS_OFFSET, BASE_OFFSET(_InterfaceTableClassName, I

8、tfName) , /接口表项,对于接口对于接口ItfName,使用宏计算使用宏计算ItfName与子类的偏移与子类的偏移.n#define IMPLEMENTS_INTERFACE_AS(RequestedItfName, BaseClassName) &IID_#RequestedItfName, ENTRY_IS_OFFSET , BASE_OFFSET(_InterfaceTableClassName, BaseClassName),/接口表项,对于请求的接口RequestedItfName,计算BaseClassName与子类的偏移.即请求即请求RequestedItfNam

9、e,返回给返回给BaseClassName,(往往用于菱形多重继承的情形.为了避免歧义,请求祖父类接口,返回父亲类接口.)#define END_INTERFACE_TABLE() 0, 0, 0 ; return table; /结尾项n我们还要定义一个函数:6HRESULT STDAPICALLTYPE InterfaceTableQueryInterface(void *pThis, const INTERFACE_ENTRY *pTable, REFIID riid, void *ppv) if ( riid= IID_IUnknown) /对于对于IUnknown接口接口 *ppv

10、= pThis + pTable-dwData;/直接加上第一项的偏移量. (IUnknown*)(*ppv)-AddRef(); /计数加1 return S_OK; else /别的接口别的接口 HRESULT hr = E_NOINTERFACE; while (pTable-pIID) /如果没有到达尾部则, (结尾项为空 ) if (riid=pTable-pIID) /比较ID *ppv = pThis + pTable-dwData;/加上偏移量. (IUnknown*)(*ppv)-AddRef(); /计数加1 hr = S_OK; break; /找到了处理完则退出 pTa

11、ble+;/否则循环遍历接口表. if (hr != S_OK) *ppv = 0; /不支持此接口 return hr; n在此基础上, 以下宏实现IUnknown接口:7先定义一个结构用于引用计数:struct AUTO_LONG LONG value; AUTO_LONG(void) : value(0) ; /构造函数使得其值为#define IMPLEMENT_UNKNOWN(ClassName) AUTO_LONG m_cRef; /引用计数变量STDMETHODIMP QueryInterface(REFIID riid, void *ppv) return InterfaceT

12、ableQueryInterface(this, GetInterfaceTable(), riid, ppv); /直接调用刚定义的InterfaceTableQueryInterfaceSTDMETHODIMP_(ULONG) AddRef(void) return InterlockedIncrement(&m_cRef.value); /InterlockedIncrement能防止多线程同时访问STDMETHODIMP_(ULONG) Release(void) ULONG res = InterlockedDecrement(&m_cRef.value); if (

13、res = 0) delete this;return res; /实现实现IUnknown定义的三个函数定义的三个函数8n现在来看应用. 对于第五章字典对象的例子:nclass CDictionary : public IDictionary , public ISpellCheckpublic : CDictionary(); CDictionary(); /构造函数,析构函数 virtual HRESULT _stdcall QueryInterface(const IID& iid, void *ppv) ; / IUnknown 成员函数 (在这里要实现,所以再次申明) vi

14、rtual ULONG_stdcall AddRef() ; virtual ULONG_stdcall Release() ; /要对以上三个函数分别实现要对以上三个函数分别实现,而且对所有的而且对所有的COM对象都是雷同的对象都是雷同的. virtual BOOL _stdcall Initialize(); / IDictionary成员函数. / ISpellCheck成员函数virtual BOOL _stdcall CheckWord (String word, String *);private : structDictWord *m_pData;char*m_DictFilen

15、ame128;/私有与功能相关的数据. int m_Ref ;/用作引用计数;1.3 应用9n我们可以写成:nclass CDictionary : public IDictionary , public ISpellCheckpublic : CDictionary(); CDictionary(); /构造函数,析构函数 / 使用宏来定义接口表,实现IUnknown 成员函数 IMPLEMENT_UNKNOWN(CDictionary) BEGIN_INTERFACE_TABLE(CDictionary) IMPLEMENT_INTERFACES(IDictionary) /请求IUnko

16、wn接口,则返回IDictionary.因为它是表的第一项. 见InterfaceTableQueryInterface的定义. IMPLEMENT_INTERFACES(ISpellCheck) END_INTERFACE_TABLE() virtual BOOL _stdcall Initialize(); / IDictionary成员函数. virtual BOOL _stdcall CheckWord (String word, String *); / ISpellCheck成员函数private : struct DictWord *m_pData;char*m_DictFile

17、name128;/与功能相关的私有数据. / int m_Ref ;不再需要单独定义引用计数; /并没有引进新技术并没有引进新技术,但是但是,更加形式化更加形式化.便于编程便于编程,提高效率提高效率.102. 多重继承下的名字冲突n2.1 潜在的缺陷潜在的缺陷: 使用多重继承使用C+类来实现COM接口,是一种非常有效的技术.编码量小,直观.建立COM规范所要求的vptr和vtbl的工作大多由编译器完成了. 使用多重继承,一个虚函数出现在多个基类中,在子类中实现后,该子类的属于多个基类的虚表中的该虚函数的表项都指都指向这个实现向这个实现. QueryInterface, AddRef, Rele

18、ase就是利用这样的方法实现的, 而且工作得很好. 这个特性对于基类中其他的函数也是成立的.但是有的时候,这就会成为一种限制.一种潜在的缺陷潜在的缺陷.n 设想一种交通工具水上飞机boatplane.它既可以象飞机一样飞,也可以象船一样在水中航行.我们做一个COM对象BoatPlane,它使用多重继承的方式实现了接口IBoat,和接口IPlane. 这两个接口都派生自接口IVehicle. IVehicle有一个函数GetMaxSpeed以返回该交通工具的最大速度.IBoat继承了此函数.同样IPlane也继承了此函数. 注意接口是定义函数的地方,它不能够实现它,(IBoat和IPlane是抽

19、象基类,GetMaxSpeed是纯虚函数). 在COM对象类BoatPlane中对此函数的实现,会导致BoatPlane中的属于IBoat和IPlane的GetMaxSpeed函数表项都指向该实现.这意味着客户得到COM对象的接口指针IBoat和IPlane时,调用GetMaxSpeed会得到同样的结果同样的结果!n多重继承下的名字冲突. 潜在的缺陷爆发了11n本章中用到的类的继承层次IUnknownIVehicleIBoatIPlanen使用多重继承的方式实现接口时的潜在缺陷n一种简单的解决方案是: IBoat不从IVehicle派生, 把GetMaxSpeed换成GetMasBoatSpe

20、ed, IPlane也不从IVehicle派生, 把GetMaxSpeed换成GetMaxPlaneSpeed. 换个名字而已.再在BoatPlane类中对这两个函数分别实现, 50KM/H, 800KM/H 问题似乎解决,但是, 我们要改变接口的继承结构吗? 而且, IBoat类的设计者十分不情愿把GetMaxSpeed换成GetMasBoatSpeed, 因为“Boat”对其而言是多余的,不雅观的.同样,IPlane的设计者也有同感. 毕竟, 人类对完美的追求是无止境的IUnknownIVehicleIBoatIPlaneBoatPlane定义GetMaxSpeed实现GetMaxSpee

21、d12n一个别致的方案:针对IBoat,设计一个中间类中间类: :struct IXBoat:public IBoat virtual HRESULT GetMaxBoatSpeed(long *pV)=0; /新增加一个纯虚函数. 此函数在COM对象中实现 HRESULT GetMaxSpeed(long *pV) return GetMaxBoatSpeed(pV); /实现了接口IBoat的GetMaxSpeed,但是,只转交给另一个函数GetMaxBoatSpeed. COM对象不再改写此函数./此类既不是COM接口类,也不是COM实现类.针对IPlane, 也作类似的处理,设计一个中

22、间类IXPlane,增加一个纯虚函数GetMaxPlaneSpeed,并把其GetMaxSpeed转交给新函数处理.2.2临时的方案13n而COM对象类:class BoatPlane:public IXBoat, public IXPlane public: / IUnknown的方法: QueryInterface,AddRef,Release /IBoat的方法: HRESULT swim(void); / IPlane的方法: HRESULT fly(void); /IXBoat的方法: HRESULT GetMaxBoatSpeed(long *pV) *pV=50; /IXPlan

23、e的方法: HRESULT GetMaxPlaneSpeed(long *pV) *pV=800;/注意没有改写接口类接口类的IBoat和IPlane的GetMaxSpeed. 只改写了中间类中间类的两个函数.COM对象的内存结构如下:14n多重继承中发生名字冲突的一种解决方案.IBoatIXBoat vptrBoatPlane:QueryInterfaceBoatPlane:AddRefBoatPlane:Release对象状态数据BoatPlane:SwimIXBoat:GetMaxSpeedBoatPlane:GetMaxBoatSpeedIXPlane vptrBoatPlane:Qu

24、eryInterfaceBoatPlane:AddRefBoatPlane:ReleaseBoatPlane:FlyIXBoat:GetMaxSpeedBoatPlane:GetMaxPlaneSpeedIPlaneIUnknownIBoat接口指针接口指针IBoat接口指针接口指针IVehicle继承自接口类的虚表(虚线框)中间类的虚表(实线框)15nIBoat-GetMaxSpeed(*pV)将在内部调用GetMaxBoatSpeed,返回50nIPlane-GetMaxSpeed(*pV)将在内部调用GetMaxPlaneSpeed,返回800n以上方案相当不错.注意,BoatPlane

25、类没有改写没有改写接口类的GetMaxSpeed方法.实际上,一旦改写了,那么,费尽心机所加的中间类,以及中间函数都将失效. 客户使用IBoat接口或IPlane接口调用GetMaxSpeed时将调用BoatPlane的实现.所有的努力白费.n这是一个悖论悖论! 接口定义了,实现却不能写它,客户还要调用. 称之为完美无缺,尚难服众.n在下面的章节中,我们有新的方法.163.使用复合(COMPOSITE)技术实现接口n3.1 COM主对象和主对象和COM子对象子对象n复合(COMPOSITE)指在一个类中包含了另一个类的实例作为其数据成员.有时也称为嵌套类. (当一个类A内嵌另一个类B时, 为方

26、便起见,称A为B的父类,或包装类,称B为A的子类. 注意与继承关系带来的基类与子类的关系区别开来. )nCOM规定了接口的结构,但是并没有规定其实现方法. 复合方式是使用C+语言实现COM接口的另一种方法. 使用复合技术可以很好地解决名字冲突问题. 方法是: 把发生名字冲突的接口以不同的把发生名字冲突的接口以不同的C+的类实现的类实现,让让COM对象类复合对象类复合(即内嵌即内嵌)这些类的实例这些类的实例. 为了让这些被内嵌(复合)进来的数据成员在客户的眼里(通过接口指针)看起来是一个对象, 通常我们在COM对象中做一个QueryInterface的主实现(master implementat

27、ion), 其他数据成员的QueryInterface都委托给这个主实现.nCOM对象类的定义如下:17class BoatPlane public:BoatPlane(void): m_Ref(0)HRESULT _stdcall QueryInterface(REFIID iid,void *ppv); ULONG _stdcall AddRef(); /这三个函数不必是虚函数这三个函数不必是虚函数 ULONG _stdcall Release(); /嵌套类从接口类派生嵌套类从接口类派生struct XBoat : public IBoat inline BoatPlane *This(

28、); /函数返回指向父类的指针函数返回指向父类的指针 HRESULT _stdcall QueryInterface(REFIID iid,void *ppv); ULONG _stdcall AddRef(); /这几个是继承自IUnknown,虚的. ULONG _stdcall Release(); HRESULT _stdcall Swim(); /具体的功能 HRESULT _stdcall GetMaxSpeed(long *pV); m_xBoat; /*n嵌套类数据成员嵌套类数据成员1, 实现了实现了IBoat接口接口. n在使用继承的方式时在使用继承的方式时,它就是它就是CO

29、M对象对象. 但这里但这里,它作为主对象的一个成员它作为主对象的一个成员. 不妨称之为不妨称之为COM子对象子对象,相应地相应地BoatPlane的实例称为的实例称为COM主对象主对象. n子对象只使用了单继承子对象只使用了单继承. 同一主对象的不同子对象都使用单继承的方式同一主对象的不同子对象都使用单继承的方式. */18struct XPlane:public IPlane /嵌套类从接口类派生嵌套类从接口类派生 inline BoatPlane *This(); /函数返回指向父类的指针函数返回指向父类的指针 HRESULT _stdcall QueryInterface(REFIID

30、iid,void *ppv); ULONG _stdcall AddRef(); ULONG _stdcall Release(); HRESULT _stdcall Fly(); HRESULT _stdcall GetMaxSpeed(long *pV); m_xPlane; /嵌套类数据成员嵌套类数据成员2 实现了实现了IPlane接口接口,另一个另一个COM子子对象对象.int m_Ref ;/ 用作引用计数char * m_pTonsOfMemForBoat; /为实现Boat功能而需要的数据.见后文 ./其他数据成员.BoatPlane的QueryInterface等函数如下:19

31、HRESULT BoatPlane:QueryInterface(const IID& iid, void *ppv)if ( iid = IID_IUnknown) *ppv=static_cast(&m_xBoat) ; /对对COM子对象进子对象进行转换后传出行转换后传出. else if ( iid = IID_IVehicle) *ppv=static_cast(&m_xBoat) ; else if ( iid = IID_IBoat) *ppv=static_cast(&m_xBoat) ; else if ( iid = IID_IPlane)

32、*ppv=static_cast(&m_xPlane) ; else *ppv=0; return E_NOINTERFACE; ( (IUnknown*)(*ppv)-AddRef(); /注意只有在对IUnknown是单继承的情况下才能使用(IUnknown*)进行转换.否则编译出错.如果使用了多继承,要转换为相应的具体的接口. return S_OK;BoatPlane 对QueryInterface的处理,并不是把指向自己的指针this进行static_cast转换, 而是对自己的成员变量即COM子对象子对象进行static_cast转换. 再传出去.3.2 COM 主对象的实

33、现20nULONG CDictionary:AddRef()m_Ref +; /跟使用继承的方式实现接口时的引用计数方法完全一致return (ULONG) m_Ref;nULONG CDictionary:Release()m_Ref -;if (m_Ref = 0 ) delete this;return 0;return (ULONG) m_Ref;21n为了维护对象的实体身份,(在这里为了实现IBoat和IPlane接口,我们使用了三个类, 其中两个派生自接口类,然后作为第三个的内嵌的数据成员) 我们不能让客户有所察觉,即“透明透明”地实现接口. 因此这里我们把COM子对象对IUnkn

34、own定义的函数的实现委托给COM主对象来完成.n因此有必要在COM子对象中访问COM主对象的成员. 它们的This内联函数就是这个目的:inline BoatPlane* BoatPlane:XBoat:This(void) return (BoatPlane*)(char*)this-offsetof(BoatPlane,m_xBoat); This函数把this指针减去类分量在类中的偏移得到父类指针. HRESULT BoatPlane:XBoat:QueryInterface(REFIID iid,void *ppv) return This()-QueryInterface(iid,

35、ppv); /通过父类指针调用父类的实现ULONG BoatPlane:XBoat: AddRef()return This()-AddRef(); /通过父类指针调用父类的实现 ULONG BoatPlane:XBoat: Release()return This()-Release(); /通过父类指针调用父类的实现/对于XPlane也采用类似的方法. 3.4 基于复合技术的基于复合技术的COM对象的内存结构对象的内存结构:COM对象的结构如下图所示:3.3 COM 子对象的实现22n基于复合技术实现COM接口 名称冲突消除了.XBoat COM子对象XBoat:QueryInterfac

36、eXBoat:AddRefXBoat:Release对象状态数据XBoat:SwimXBoat:GetMaxSpeedIBoatXPlane COM子对象XPlane:QueryInterfaceXPlane:AddRefXPlane:ReleaseXPlane:FlyXPlane:GetMaxSpeedIPlaneIUnknownBoatPlane的QueryInterface AddRef Release等函数XBoat的Swim GetMaxSpeed等函数XPlane的Fly GetMaxSpeed等函数IVehicle另一个COM子对象的虚表,单继承自其接口COM子对象的虚表,单继承

37、自其接口, 注意非COM主对象的虚表!注意是子对象,而非虚表指针234. 针对接口的引用计数n4.1 需求需求n使用复合技术需要更多的编码,而且,复合技术产生的代码的质量也可能不如使用多重继承的方式好. n复合技术实现接口成功地消除了名字冲突. 复合技术之所以能够做到这一点是因为它没有多重继承所固有的“潜在的缺陷”, 如果这个缺陷会影响功能的话. 实际上,利用复合技术的这一个优势, 我们还能够实现针对接口针对接口的引用计数.n在此之前,我们所谓的引用计数都是针对对象的. 一个对象的所有接口都对同一个计数变量进行操作.对象无法区分是哪个接口对其进行操作的.而且,实际上,通常我们也不必区分它.n但

38、是,存在这样的情况, 我们的COM对象越来越复杂,功能越来越多.(想一想我们的手机) COM对象为实现不同的接口准备了完全不同的资源,如果暂时不使用其中的某个接口的话,我们完全可以对其所需要的资源暂时不予分配.(我们打电话的时候一定要把摄像头打开吗?).而把分配工作放到必要时进行,而且,也要及时地释放.24n仍然考虑水上飞机,在BoatPlane类中我们定义了一个成员变量: char * m_pTonsOfMemForBoat; 这个成员变量只在在swim函数中要使用.也就是说,只有IBoat指针会使用它.而与IPlane指针无关.n假设m_pTonsOfMemForBoat需要分配一个很大的

39、内存空间.我们当然希望只在必要的时候分配.然而,如果使用多重继承的方式实现COM接口,那么意味者所有的虚表中的AddRef和Release项都只指向同一个实现.也即我们无法从引用计数中区分出IBoat接口来.当然,对于分配过程我们还是有点办法:HRESULT BoatPlane :QueryInterface(REFIID iid,void *ppv) if(iid=IID_Boat) if(m_pTonsOfMemForBoat=NULL) m_pTonsOfMemForBoat=new char1024*1024*10; /任务只完成了一半, 10M的内存只在最需要的时候分配. *ppv=

40、static_cast(&m_xBoat); else if 但是,我们无法知道什么时候释放掉.因为我们不能鉴别IBoat和IPlane所发出的Release调用.n可见, 针对接口的引用计数有明确的应用需求. 实现方法如下:25我们使用复合技术来实现针对接口的引用计数. COM 子对象XBoat的引用计数不再简单地委托给COM主对象实现n在上一节的BoatPlane定义中增加一个成员变量int m_BoatRef; 在BoatPlane的构造函数中被赋值0. 除了XBoat的AddRef和Release函数变为:nULONG BoatPlane:XBoat: AddRef() ULO

41、NG res=InterLockedIncrement(&m_BoatRef); if(res=1) This -m_pTonsOfMemForBoat=new char1024*1024*10; / 只在必须分配内存的时候才分配必须分配内存的时候才分配. This-AddRef(); /只在第一次调用一次主对象的AddRef,在主对象中备案. return res;4.2 实现方法26ULONG BoatPlane:XBoat: Release()ULONG res=InterLockedDecrement(&m_BoatRef); if(res=0) delete This

42、 -m_pTonsOfMemForBoat ; / 及时地释放内存及时地释放内存. IPlane接口并不需要它. 打电话时关掉摄像头! This-Release(); /最后一次调用一次主对象的Release;通知主对象, 不必为此子对象而保持引用了 return res;XPlane的引用计数函数保持不变.客户的使用完全与以前一致.27n为了使得此技术能正常工作,必须保证所有的接口指针用户遵从COM规范的要求, Release调用必须作用在它对应的AddRef指针上. 即AddRef和Release必须完全保持配对.因此在标准的QueryInterface中,在ppv指针赋值后,要使用 (I

43、Unkown*)(*ppv)-AddRef(); /使用新指针 而不能使用 AddRef(); /使用旧指针.n新旧指针指向同一个COM对象的接口, 它们有可能指的同一个对象.(在多重继承的情况下,同一个主对象同一个主对象),也有可能指的不同的对象(在复合技术下,不同的子对象不同的子对象). 在前者无论使用谁调用AddRef都是一样的,在后者则有可能不同.为了一致起见,应遵循COM规范,使用新指针.n以上方案可以实现针对接口的引用计数,从而实现资源的动态最优分配和释放.285.多重继承与复合的结合n多重继承虽然存在“潜在的缺陷”,但是, 如果它正好满足我们的需求, 实际上,大多数时候是满足的,

44、而且具有编码简单,代码质量高等优点. 而复合技术则能避免可能的名字冲突, 而且可以实现对接口的引用计数,提高程序的运行效率. 我们可以综合运用综合运用以上两种方法, 在一个COM对象中,以不同的方法实现不同要求的接口.n仍然考虑水上飞机, 我们可以这样来实现它的接口: n使用继承的方式实现IPlane接口,n使用复合的方式实现IBoat接口(IBoat不是有特殊的引用计数要求吗?).nCOM对象的定义如下:29class BoatPlane: public IPlane /IPlane接口通过继承的方式实现 public:BoatPlane(void): m_Ref(0) HRESULT _s

45、tdcall QueryInterface(REFIID iid,void *ppv); ULONG _stdcall AddRef(); /这三个函数通过IPlane从IUnknown继承而来 ULONG _stdcall Release(); /这里改写虚函数,而不像前一节,是单纯的函数 HRESULT _stdcall Fly(); /IPlane的函数 HRESULT _stdcall GetMaxSpeed(long *pV);/IVehicle的函数 struct XBoat:public IBoat inline BoatPlane *This(); /函数返回指向父类的指针 H

46、RESULT _stdcall QueryInterface(REFIID iid,void *ppv); ULONG _stdcall AddRef(); ULONG _stdcall Release(); HRESULT _stdcall Swim(); HRESULT _stdcall GetMaxSpeed(long *pV); /COM子对象的实现可以与主对象的实现不一致. 通过IBoat接口和IPlane接口能得到不同的结果. m_xBoat; /嵌套类数据成员,COM子对象 实现了IBoat接口.int m_Ref ; / 用作主对象的引用计数int m_BoatRef; /用作

47、COM子对象 IBoat接口的引用计数char * m_pTonsOfMemForBoat; ./其他数据成员. 主对象的QueryInterface函数如下:30HRESULT BoatPlane:QueryInterface(const IID& iid, void *ppv) if ( iid = IID_IUnknown) *ppv=static_cast(this) /也可以是 /*ppv=static_cast(&m_xBoat) ; else if ( iid = IID_IVehicle) *ppv=static_cast(this) /也可以是 / *ppv=

48、static_cast(&m_xBoat) ; else if ( iid = IID_IBoat) *ppv=static_cast(&m_xBoat) ; /把把COM子对象传出子对象传出 else if ( iid = IID_IPlane) *ppv=static_cast(this); /把把COM主对象传出主对象传出 else *ppv=0; return E_NOINTERFACE; ( (IUnknown*)(*ppv)-AddRef(); /增加引用计数 return S_OK; nIPlane接口调用QueryInterface当然是上述函数,而IBoat的

49、QueryInterface也应该委托给它. 其他的函数的实现方式都与以前一样.n客户得到的IPlane接口是指向COM主对象主对象的,客户得到的IBoat接口是指向BoatPlane的成员变量m_xBoat即COM子对象子对象的.接口的转换过程请自行分析.316.复合技术的表格驱动n其实,我们已经看到,使用继承或使用复合来实现接口的差别并不大, 这两种技术可以和平共处. 我们曾利用表格驱动的方式实现了多重继承方式下的COM接口的QueryInterface函数.实际上,对于复合技术,也可以使用表格驱动的方式来实现它的QueryInterface函数.n表格驱动的本质是把接口的IID号和COM

50、对象到这个IID号所代表的接口类子对象“subobject”的偏移联系起来.注意,我们的QueryInterface函数是在COM对象中实现的.而接口调用此函数,要么是通过虚函数改写的方式,要么是通过一个指向父类指针的方式进行委托调用. 无论哪种方式都是调用的COM对象定义的QueryInterface, 从这里(COM对象的this)出发, 如果有从COM对象到接口类子对象的偏移.(注意,这个偏移,有可能是从子类到基类的偏移,也有可能是从父类到嵌套子类的基类的偏移),我们当然可以得到接口类子对象.也就是接口指针.n上一段话的意思是说, 为了实现支持复合的表格驱动,只需要添加使用复合技术的接口

51、的接口表项, 而QueryInterface函数,以及InterfaceTableQueryInterface函数,(见前面章节) 都不用更改.32n定义如下的宏:n#define COMPOSITE_OFFSET(ClassName, BaseName, MemberType, MemberName) (DWORD(static_cast(reinterpret_cast(0 x10000000 + offsetof(ClassName, MemberName) - 0 x10000000)nreinterpret_cast(0 x10000000 + offsetof(ClassName,

52、 MemberName) 0 x10000000加上父类到嵌套成员的偏移转换为嵌套类n(static_cast(嵌套类指针)把嵌套类指针转换为它的基类指针即接口指针.这里由编译器进行计算偏移. n(DWORD)接口指针- 0 x10000000接口指针的绝对数字减去父类的起始地址0 x10000000. 得到父类到嵌套父类到嵌套类的基类的偏移类的基类的偏移.0 x10000 x1012ClassName 父类MemberType 嵌套类 第一步 偏移 第三步BaseName 嵌套类的基类 即接口类 第二步33#define BEGIN_INTERFACE_TABLE(ClassName) ty

53、pedef ClassName _InterfaceTableClassName;#define IMPLEMENTS_INTERFACE_WITH_COMPOSITE(RequestedItfName, DataMemberType, DataMemberName) &IID_#RequestedItfName, ENTRY_IS_OFFSET , COMPOSITE_OFFSET(_InterfaceTableClassName, RequestedItfName, DataMemberType, DataMemberName) ,以上宏把复合的接口的偏移表项加入到接口表中去.34

54、#define IMPLEMENT_COMPOSITE_UNKNOWN(OuterClassName, InnerClassName, DataMemberName) OuterClassName *This() return (OuterClassName*)(char*)this - offsetof(OuterClassName, DataMemberName); STDMETHODIMP QueryInterface(REFIID riid, void *ppv) return This()-QueryInterface(riid, ppv);STDMETHODIMP_(ULONG)

55、AddRef(void) return This()-AddRef(); STDMETHODIMP_(ULONG) Release(void) return This()-Release();以上宏为复合的接口实现IUnknown定义的函数QueryInterface AddRef 和Release35n使用这些宏来完成上一节的水上飞机(两种不同方式实现的接口):class BoatPlane: public IPlane /IPlane接口通过继承的方式实现 public: struct XBoat:public IBoat /IBoat接口使用复合方式实现 IMPLEMENT_COMPOS

56、ITE_UNKNOWN (BoatPlane,XBoat,m_xBoat); /复合接口实现IUnknown接口 HRESULT _stdcall Swim(); HRESULT _stdcall GetMaxSpeed(long *pV); m_xBoat; /嵌套类数据成员,实现了IBoat接口. IMPLEMENT_UNKNOWN(BoatPlane) / IPlane接口实现IUnknown BEGIN_INTERFACE_TABLE(BoatPlane) IMPLEMENT_INTERFACES(IPlane) IMPLEMENT_INTERFACES_AS(IVehicle,IPl

57、ane)/如果请求IVehicle则返回IPlane,当然也可以返回IBoat. IMPLEMENTS_INTERFACE_WITH_COMPOSITE (IBoat,XBoat,m_xBoat) /把复合接口加入到接口表中 END_INTERFACE_TABLE() HRESULT _stdcall Fly(); /IPlane的函数 HRESULT _stdcall GetMaxSpeed(long *pV);/IVehicle的函数 char * m_pTonsOfMemForBoat; /注意,这里没有实现针对接口的引用技术,如果要实现不能使用IMPLEMENT_COMPOSITE_U

58、NKNOWN宏367.动态复合接口n事实上,除了使用单独的引用计数外,复合接口还可以进一步优化.一个COM子对象直到客户请求它的复合接口的时候才真正创建,可以进一步优化资源. 在特定的场合下有其重要的用途.这样的接口称为动态复合接口,也称为tearoff接口.class BoatPlane: public IPlane /IPlane接口通过继承的方式实现 public: BoatPlane(void): m_Ref(0) HRESULT _stdcall QueryInterface(REFIID iid,void *ppv); ULONG _stdcall AddRef(); ULONG

59、_stdcall Release(); HRESULT _stdcall Fly(); /IVehicle的函数 HRESULT _stdcall GetMaxSpeed(long *pV); intm_Ref ; / 用作主对象的引用计数 char * m_pTonsOfMemForBoat; ./其他数据成员. IBoat * m_pBoat; /指针用来保存动态创建的接口37struct XBoat:public:IBoat /嵌套类实现了IBoat接口. XBoat(BoatPlane *pThis); /构造函数 int m_BoatRef; / 自己负责自己的计数 BoatPlan

60、e *m_pThis; /指向父类的指针 inline BoatPlane *This()return m_pThis; /返回指向父类的指针 HRESULT _stdcall QueryInterface(REFIID iid,void *ppv); ULONG _stdcall AddRef(); ULONG _stdcall Release(); HRESULT _stdcall Swim(); HRESULT _stdcall GetMaxSpeed(long *pV); /注意只定义了嵌套类注意只定义了嵌套类,没有数据成员没有数据成员. 即只有即只有COM子对象的类定子对象的类定义义, 而没有而没有COM子对象本身子对象本身.n主对象第一次接收到IBoat接口请求时,动态地创建一个新的复合接口. 这个工作当然是在QueryInter

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论