您的位置:首页 > 其它

COM技术内幕-读书笔记-系列之三

2008-12-18 00:19 288 查看
第3章 QueryInteface
=================================================================================
未经许可,也可转载,但请注明出处!希望能够与各位初学COM的网友共勉:-)
[b]======================================================================= [/b]

本节提要:本章重点讲解了COM都必须继承的IUnkown接口的成员函数之一的QureryInterface函数。QureryInterface是编写COM组件的过程同编写C++类的过程区分开的一种特性。COM组件大部分的灵活性及其封装的能力都是由QureryInterface提供的。它使得客户能够在运行时决定一个组件所提供的功能,并最大限度的利用动态链接。通过将组件的功能完全的向客户隐藏起来,QureryInterface可以尽可能的防止组件实现的变化对客户造成的影响。同时QureryInterface也是一种极好的实现对组件版本无缝升级处理的机制。这种机制使得组件的新旧不同版本可以互操作,从而能够一块工作。

1、IUnkonwn为何物?
IUnkonwn是COM接口的基类,所有的COM接口都必须继承IUnkown。微软在Win32SDK中UNKNWN.H中定义该接口如下:




2、为什么COM接口必须继承IUnkown?




仔细阅读原书中这段话之后,重点归纳如下(也算是从侧面回答这个问题):
1、每个接口的vtbl中的前三个函数都相同,依次为QueryInterface、AddRef和Release。
2、客户可以通过QueryInterface来查询该组件所支持的某个接口并获得接口的指针。
3、AddRef和Release用来增加或减少对组件的引用,以便不用时自动被服务器卸载。
3、所有的COM接口都可以当着IUnkown来处理。
4、所有接口都支持QueryInterface接口查询。
5、若某个接口的vtbl中前三个函数不是Q/A/R,那么它将不是一个COM接口。
6、组件的任何一个接口都可以被客户用来获取该组件所支持的其它所有接口。假如某个组件有A、B、C三个接口,如果知道B接口,则可以查询到A和C接口,反之亦然。
7、由于客户所有的接口指针同时也将是IUnkown指针(由4可知),因此客户无需单独维护一个代表组件的指针。
8、两个不同的接口,当且仅当它们返回相同的IUnkown指针,才被认为是同属于一个组件。
9、参考第7条“ QureryInterface实现的规则有哪些?”。

3、如何理解QueryInterface函数传入的两个参数?
函数定义:HRESULT _stdcall QueryInterface(const IID &iid,void **ppv);
第1个参数:iid
iid是IID结构,该结构比较庞大,所以参数使用引用传递。该参数是被查询的接口的iid,根据该接口iid,可以查询接口的指针地址。
第2个参数:ppv
ppv是一个二级指针,存储所查询到的iid接口的指针地址。*ppv是该接口指针值。

4、为什么QueryInterface的参数ppv要使用二级指针?
由上面3可知,ppv存储的是被查询到的接口指针。我们知道,在函数传递的参数中,如果该参数要被改变,那么必须是实参传递,也就是地址传递。假设p是一个指针,如果要改变一个指针参数p的数值,那么必须传入该指针的地址&p,也就是&p(即**pp)。所以这就是我们所看到的QureryInterface(IID_IXX,(void**)&ppv)函数,其中(void**)是对指针ppv进行强制转换。

5、为什么指针类型转换有可能会造成指针数值改变?
通常一种指针转换为另一种类型的指针,其数值不会变化,但是为了支持多重继承,在某些情况下,C++必须改变类指针的数值。(!!!必须注意到此负面效果)
下图是一个多重继承的简单实例:




注:
1、CA的this指针指向IX的虚拟表,因此可以在不改变CA的this指针情况下来用它代替IX指针。
2、CA的this指针并不等于IY,所以将CA的this指针转化为IY指针,必须要修改其指针数值,为完成此修改,编译器把IY的虚拟表指针的偏移量加到CA的this指针上,如下:
IY *pC=pA;转换成:IY *pC=(char *)pA + DeltaIY


6、分析例子代码

仔细阅读P35, $3.1.7完整的例子并运行,理解COM的设计思想。
下图是从本章代码中理出的基本框架及其关键部分函数的实现:



7、QureryInterface实现的规则有哪些?
规则1QureryInterface返回的总是同一IUnkown地址。
如果QureryInterface的实现不遵循此规则,将无法决定两个接口是否属于同一组件。
规则2若客户曾经获取过某个接口,那么它将总能获取此接口。
如果客户不能获取它曾经使用过的某个接口,则说明组件的接口集是不固定的,客户也将无法通过编程的方法来决定一个组件到底具有一些什么样的功能。
规则3客户可以再次获取已拥有的接口。
规则4客户可以返回到起始接口。
规则5若能从从某个接口获取某个特定的接口,那么可以从任意接口都可以获取此接口。
规则6客户能够使用任何IUnkown接口获取该组件所支持的任何接口。(本条为个人归纳)
制定上述规则的目的完全是为了使QureryInterface使用起来更为简单、更富有逻辑性、更一致性以及更具有确定性。不过幸运的是,实现上述规则并不难,并且只有组件按照这些规则正确的实现了QureryInterface时,客户才不会为此担心。

8、什么是接口集?
组件所支持的接口集就是QureryInterface在其if-else分支结构中能够为之返回接口指针的那些接口。

9、为什么说QureryInterface是COM中最重要的部分?
因为QureryInterface实际上定义了一个组件,组件所支持的接口集都在QureryInterface的if-else分支结构中被决定,而不是由实现C++的类组件决定。实现组件的类的继承层次关系也不能决定组件,一个组件仅仅由QureryInterface的实现决定。

10、客户如何知道组件的接口?
由于客户并不知道QureryInterface的实现,也不像C++中的拥有类的头文件,所以客户了解组件的唯一方法就是使用QureryInterface来查询。

11、如何快速查询接口集中的某个特定的接口?
在许多情况下,客户可以使用只实现某个特定接口集的组件,为了查询某个组件是否支持某个特定的接口而创建该组件并逐一查询该组件接口,无疑这是比较浪费时间的。为了更有效的解决这个问题,某个特定的接口集可以用一个组件类别来识别,各组件可以申明它是否属于某个特定的组件类别,这样客户可以在不创建该组件的情况下获取此种信息。

12、如何处理组件新的版本?

当组件发布一个新的接口并被用户使用之后,此接口将绝不发生任何变化。当我们要升级该接口时,可以建立一个新的接口并为它指定新的IID。当客户用QureryInterface查询老的IID时,它将返回老的接口,而当它查询新的IID时,它将返回升级过的接口。QureryInterface而言,一个IID就是一个接口。
接口的标识(IID)是同其版本绑在一起的。也就是说该接口升级为新的版本,IID也需要更新。
假设有一个组件Bronce,它拥有一个IFly接口,使用该组件的客户为Pilot。
经过一段时间后,组件和客户都进行了升级。Bronce组件升级为FastBronce,其接口也升级为IFastFly。Pilot客户升级为FastPilot,既支持组件新的接口也支持老的接口。下图给出了它们之间各种可能的运行组合:



由上图可知,不论按何种组合,客户和组件都能够正常运行,因此该升级是非常平滑而又无缝的,且也是非常之有效的。

13、何时需要建立组件的新版本?
为使COM版本处理多个机制能够起作用,我们在为已有的接口制定新的IID时应该要非常谨慎,当改变了下列任何条件之一时,都应该为接口制定新的IID:
1、接口中函数的数目。
2、接口中函数的顺序。
3、某个函数的参数。
4、某个函数的参数的顺序。
5、某个函数参数的类型。
6、函数可能的返回值。
7、函数返回值的类型。
8、函数参数的含义。
9、接口中函数的含义。
总之,只要是所做的修改如果会导致已有客户不能正常运行,都应该为接口制定新的ID。如果能够同时修改客户和组件,则可以灵活掌握上述条款。

14、如何命名不同的组件版本?
在建立了新的版本之后,也应当相应的修改其名称。COM关于新版本名称的约定是在老的版本之后加一个数字。如IFly新的版本名称应该是IFly2。

15、本章代码如何在VC6的工程中运行?
本章实例代码比较简单,按下列步骤即可:
1、新建一个空的console空的工程。
2、将随书源代码中的iface.c文件COPY到该工程目录下。
3、将该文件加入到该工程中。
4、编译运行即OK。

BENKONG2008-12-18
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: