六com接口的其他实现方法内容摘要:

作为第三个的内嵌的数据成员 ) 我们不能让客户有所察觉 ,即 “ 透明 ” 地实现接口 . 因此这里我们把 COM子对象对 IUnknown定义的函数的实现委托给 COM主对象来完成 .  因此有必要在 COM子对象中访问 COM主对象的成员 . 它们的 This内联函数就是这个目的 : inline BoatPlane* BoatPlane::XBoat::This(void) { return (BoatPlane*)((char*)thisoffsetof(BoatPlane,m_xBoat))。 } This函数把 this指针减去类分量在类中的偏移得到父类指针 . HRESULT BoatPlane::XBoat::QueryInterface(REFIID iid,void **ppv) { return This()QueryInterface(iid,ppv)。 } //通过父类指针调用父类的实现 ULONG BoatPlane::XBoat:: AddRef() {return This()AddRef()。 } //通过父类指针调用父类的实现 ULONG BoatPlane::XBoat:: Release() {return This()Release()。 } //通过父类指针调用父类的实现 //对于 XPlane也采用类似的方法 . 基于复合技术的 COM对象的内存结构 : COM对象的结构如下图所示 : COM 子对象的实现 22  基于复合技术实现 COM接口 名称冲突消除了 . XBoat COM子对象 XBoat::QueryInterface XBoat::AddRef XBoat::Release 对象状态数据 XBoat::Swim XBoat::GetMaxSpeed IBoat XPlane COM子对象 XPlane::QueryInterface XPlane::AddRef XPlane::Release XPlane::Fly XPlane::GetMaxSpeed IPlane IUnknown BoatPlane的 QueryInterface AddRef Release 等函数 XBoat的 Swim GetMaxSpeed 等函数 XPlane的 Fly GetMaxSpeed 等函数 IVehicle 另一个 COM子对象的虚表 ,单继承自其接口 COM子对象的虚表 ,单继承自其接口 , 注意非 COM主对象的虚表 ! 注意是子对象 ,而非虚表指针 23 4. 针对接口的引用计数  需求  使用复合技术需要更多的编码 ,而且 ,复合技术产生的代码的质量也可能不如使用多重继承的方式好 .  复合技术实现接口成功地消除了名字冲突 . 复合技术之所以能够做到这一点是因为它没有多重继承所固有的 “ 潜在的缺陷 ” , 如果这个缺陷会影响功能的话 . 实际上 ,利用复合技术的这一个优势 , 我们还能够实现 针对接口 的引用计数 .  在此之前 ,我们所谓的引用计数都是针对对象的 . 一个对象的所有接口都对同一个计数变量进行操作 .对象无法区分是哪个接口对其进行操作的 .而且 ,实际上 ,通常我们也不必区分它 .  但是 ,存在这样的情况 , 我们的 COM对象越来越复杂 ,功能越来越多 .(想一想我们的手机 ) COM对象为实现不同的接口准备了完全不同的资源 ,如果暂时不使用其中的某个接口的话 ,我们完全可以对其所需要的资源暂时不予分配 .(我们打电话的时候一定要把摄像头打开吗 ?).而把分配工作放到必要时进行 ,而且 ,也要及时地释放 . 24  仍然考虑水上飞机 ,在 BoatPlane类中我们定义了一个成员变量 : char * m_pTonsOfMemForBoat。 这个成员变量只在 在 swim函数中要使用 .也就是说 ,只有 IBoat指针会使用它 .而与 IPlane指针无关 .  假设 m_pTonsOfMemForBoat需要分配一个很大的内存空间 .我们当然希望只在必要的时候分配 .然而 ,如果使用多重继承的方式实现 COM接口 ,那么意味者所有的虚表中的 AddRef和 Release项都只指向同一个实现 .也即我们无法从引用计数中区分出 IBoat接口来 .当然 ,对于分配过程我们还是有点办法 : HRESULT BoatPlane ::QueryInterface(REFIID iid,void **ppv) { if(iid=IID_Boat) { if(m_pTonsOfMemForBoat==NULL) m_pTonsOfMemForBoat=new char[1024*1024*10]。 //任务只完成了一半 , 10M的内存只在最需要的时候分配 . *ppv=static_castIBoat*(amp。 m_xBoat)。 } else if …… } 但是 ,我们无法知道什么时候释放掉 .因为我们不能鉴别 IBoat和 IPlane所发出的 Release调用 .  可见 , 针对接口的引用计数有明确的应用需求 . 实现方法如下 : 25 我们使用复合技术来实现针对接口的引用计数 . COM 子对象 XBoat的引用计数不再简单地委托给 COM主对象实现  在上一节的 BoatPlane定义中增加一个成员变量 int m_BoatRef。 在BoatPlane的构造函数中被赋值 0. 除了 XBoat的 AddRef和 Release函数变为 :  ULONG BoatPlane::XBoat:: AddRef() { ULONG res=InterLockedIncrement(amp。 m_BoatRef)。 if(res==1) { This m_pTonsOfMemForBoat=new char[1024*1024*10]。 // 只在 必须分配内存的时候才分配 . ThisAddRef()。 } //只在第一次调用一次主对象的 AddRef,在主对象中备案 . return res。 } 实现方法 26 ULONG BoatPlane::XBoat:: Release() {ULONG res=InterLockedDecrement(amp。 m_BoatRef)。 if(res==0) { delete []This m_pTonsOfMemForBoat。 // 及时地释放内存 . IPlane接口并不需要它 . 打电话时关掉摄像头 ! ThisRelease()。 } //最后一次调用一次主对象的 Release。 通知主对象 , 不必为此子对象而保持引用了 return res。 } XPlane的引用计数函数保持不变 . 客户的使用完全与以前一致 . 27  为了使得此技术能正常工作 ,必须保证所有的接口指针用户遵从COM规范的要求 , Release调用必须作用在它对应的 AddRef指针上 . 即 AddRef和 Release必须完全保持配对 .因此在标准的QueryInterface中 ,在 ppv指针赋值后 ,要使用 ((IUnkown*)(*ppv))AddRef()。 //使用新指针 而不能使用 AddRef()。 //使用旧指针 .  新旧指针指向同一个 COM对象的接口 , 它们有可能指的同一个对象 .(在多重继承的情况下 ,同一个主对象 ),也有可能指的不同的对象(在复合技术下 ,不同的子对象 ). 在前者无论使用谁调用 AddRef都是一样的 ,在后者则有可能不同 .为了一致起见 ,应遵循 COM规范 ,使用新指针 .  以上方案可以实现针对接口的引用计数 ,从而实现资源的动态最优分配和释放 . 28  多重继承虽然存在 “ 潜在的缺陷 ” ,但是 , 如果它正好满足我们的需求 , 实际上 ,大多数时候是满足的 ,而且具有编码简单 ,代码质量高等优点 . 而复合技术则能避免可能的名字冲突 , 而且可以实现对接口的引用计数 ,提高程序的运行效率 . 我们可以 综合运用 以上两种方法 , 在一个 COM对象中 ,以不同的方法实现不同要求的接口 .  仍然考虑水上飞机 , 我们可以这样来实现它的接口 : 1. 使用继承的方式实现 IPlane接口 , 2. 使用复合的方式实现 IBoat接口 (IBoat不是有特殊的引用计数要求吗 ?).  COM对象的定义如下 : 29 class BoatPlane: public IPlane //IPlane接口通过继承的方式实现 { public:BoatPlane(void): m_Ref(0){} HRESULT _stdcall 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()。 //函数返回指向父类的指针 HRESULT _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。 //用作 COM子对象 IBoat接口的引用计数 char * m_pTonsOfMemForBoat。 … .//其他数据成员 . } 主对象的 QueryInterface函数如下 : 30 HRESULT BoatPlane::QueryInterface(const IIDamp。 iid, void **ppv) { if ( iid == IID_IUnknown) *ppv=static_castIUnknown*(this) //也可以是 //*ppv=static_castIUnknown*(amp。 m_xBoat)。 else if ( iid == IID_IVehicle) *ppv=static_castIVehicle*(this) //也可以是 // *ppv=static_castIVehicle*(amp。 m_xBoat)。 else if ( iid == IID_IBoat) *ppv=static_castIBoat*(amp。 m_xBoat)。 //把 COM子对象传出 else if ( iid == IID_IPlane) *ppv=static_castIPlane*(this)。 //把 COM主对象传出 else { *ppv=0。 return E_NOINTERFACE。 } ( (IUnknown*)(*ppv))AddRef()。 //增加引用计数 return S_OK。 }  IPlane接口调用 QueryInterface当然是上述函数 ,而 IBoat的QueryInterface也应该委托给它 . 其他的函数的实现方式都与以前一样 .  客户得到的 IPlane接口是指向 COM主对象 的 ,客户得到的 IBoat接口是指向 BoatPlane的成员变量 m_xBoat即 COM子对象 的 .接口的转换过程请自行分析 . 31  其实 ,我们已经看到 ,使用继承或使用复合来实现接口的差别并不大 , 这两种技术可以和平共处 . 我们曾利用表格驱动的方式实现了多重继承方式下的 COM接。
阅读剩余 0%
本站所有文章资讯、展示的图片素材等内容均为注册用户上传(部分报媒/平媒内容转载自网络合作媒体),仅供学习参考。 用户通过本站上传、发布的任何内容的知识产权归属用户或原始著作权人所有。如有侵犯您的版权,请联系我们反馈本站将在三个工作日内改正。