COM技术内幕

第1章 组件
 
 ===================================================================================
未经许可,也可转载,但请注明出处!希望能够与各位初学COM的网友共勉:-)
=======================================================================
 
本节要点:本章重点讨论了组件的优点以及软件工业技术对组件的需求。为了解决软件的频繁升级问题,最有效的方法就是将应用程序分成一些小的组件,然后这些组件在运行时组装起来形成所需要的应用程序,并且保证每一个组件在不影响其它组件的情况下被升级。为此,COM提供了编写组件的一个标准方法。遵循COM标准的组件可以被组合起来以形成应用程序。至于这些组件是谁编写的、如何实现的,都是无关紧要的。每一个COM组件均可同其它组件一起使用。实现这种可动态改变组件的关键问题是信息的封装。对于封装,COM是通过组件和客户之间的接口来实现的。

 
 
1、什么是COM?
COM: Componet Object Model(组件对象模型),是开发软件组件的方法之一。它也是一个如何建立可动态互变组件的规范,它提供了为保证能够互操作,客户和组件都必须遵循的一些标准。(P6 $1.3,也说明了组件并不就是COM)

2、什么是组件?
组件实际上是一些小的二进制可执行的程序,它可以给应用程序、操作系统以及其它组件提供一些服务。组件可以在运行时、在不重新链接或编译应用程序的情况下被卸载或替换掉。多个COM对象可以连接起来以形成应用程序或组件系统。

3、由组件构成的应用程序有何好处?


 
将单个应用程序分割为多个组件组成的程序(如上图所示),其带来的好处就是,随着技术的发展可以用新的组件取代原有的组件,从而逐渐使软件日臻完善。这样无需对整个程序进行更改编译,从而更有利于软件的维护和升级(如下图所示)。另外还可以动态的从程序中插入和卸载。再次组件的使用可以做到与语言的无关性。
 

 
4、什么是分布式组件?它们如何工作?
分布组件就是分布在网络上的远地组件(暂且本人这样认为)。由分布在网络上的多个组件构成分布式应用程序。
下图是一个使用了远程组件的分布式应用程序工作示意图。


 
其本地上的两个组件C和组件D被放在网络上的远程机上,取而代之的是两个新的组件:远程C和远程D(实际上就是代理/存根DLL),这两个组件的作用是将其他组件发来的请求通过网络发给组件C和组件D。本地机器上的应用程序并不需要知道实际所应用的组件到底在何处。类似地,远地组件也不需要知道他们是否位于远地。这样,通过加入合适的远地组件,应用程序完全不需要知道实际的组件到底在哪里,具体情况可参考本书P219 $10.4远程访问能力。

5、对组件有何要求?
为了实现组件可以方便的从程序中动态插入和卸载,所有组件必须满足下面两个条件:
1、组件必须能够动态链接。
2、必须能够封装其内部实现细节,对客户做到完全透明。

6、如何将客户和组件的实现很好的隔离开来?
1、组件必须将其实现所用的编程语言封装起来。任一客户都应能使用任一组件,不论它们是用什么编程语言实现的。将实现用的编程语言暴露出来只会在组件及客户间引入新的依赖。
2、组件必须以二进制的形式发布。如果想将实现组件的编程语言隐藏起来,那么在发布时,它们必须是已被编译、链接好并且马上就可以投入使用的。
3、组件必须可以在不妨碍已有用户的情况下被升级。一个组件的新版本必须既能够同老版本的客户一起使用,也可以同心版本的客户一起使用。
4、组件在网络上的位置必须可以被透明的重新分配。组件及使用它的程序应能够在同一进程中、不同的进程中或不同的机器上运行。否则,当将某个本地组件移动到网络上的另外某个地方时,客户程序必须被重新编译。

7、什么是COM规范?
COM规范就是一套为组件架构设置的标准文档。遵循COM规范编写的组件将能满足对组件架构的所有要求。本书所开发的所有组件都遵守此标准。
 
8、COM是…?
1、COM组件是以Win32动态链接库(DLL)或者可执行文件(EXE)的形式发布的可执行二进制代码所组成的。
2、COM组件是动态链接的。
3、COM组件是完全与语言无关的。
4、COM组件是可以在网络上透明的被重新分配位置的。
5、COM组件是在兼容老客户的情况下悄然升级的。
6、COM组件是一种给其它应用程序提供面向对象的API或服务的极好方法。
7、COM是组织软件的一种方法。
 
9、COM不是…?
1、COM不是一种计算机语言。
2、COM不是DLL。
3、COM不是函数集。
4、COM不是类库。

10、使用COM有那些好处?
1、提供了一个所有组件都应遵循的标准。
2、允许使用组件的多个不同的版本,而这一点对于用户而言几乎是透明的。
3、使得可以按相同的方式来处理类似的组件。
4、定义了一个与语言无关的架构。
5、支持对远程组件的透明连接。

** 什么是客户?
对如一个应用程序或者组件,如果它使用了其它组件,相对于被使用的组件而言,我们将它称之为客户。(P5)
 


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/benkong2008/archive/2008/12/17/3537101.aspx

 

 

第2章 接口
 
=================================================================================
未经许可,也可转载,但请注明出处!希望能够与各位初学COM的网友共勉:-)

=======================================================================
 
本节提要:本章从接口的一般性概念讲起,由浅入深,一直讲到接口的内存结构,并且介绍了什么是接口、如何实现接口以及如何使用接口。另外还介绍了如何在C++中使用纯抽象基类来实现接口,也知道C++编译器为纯抽象类所生成的内存结构同COM接口所要求的内存结构是相同的。不过本章所讲解的接口还不是真正意义上的COM接口。再次,通过本章的学习,也让我们知道接口可以通过封装其内部实现细节而使一个由组件构成的系统免受外界变化的影响,只要接口不发生变化,那么客户或组件可以在不影响整个系统正常运行的情况下自由的变化。这使得我们可以用新的组件来代替老的组件,客户也可以一致的对待实现同一接口的所有组件。

 

 
1、什么是接口?
接口是组件与客户进行通讯的协议,客户通过接口来访问组件。接口有一个二进制标准,也就是说,表示接口的内存块必需具有一定的结构。幸运的是,使用纯抽象基类时,许多C++编译器均可生成具有如此结构的内存块。

2、接口与组件的关系?
接口是组件与客户沟通的窗口,组件本身只不过是是接口实现的细节。一个系统可由多个组件构成,一个组件是一个或多个接口的集合,一个接口是一系列函数的集合。

3、使用COM接口有哪些优点?
1、接口可以保护系统免受外界的影响。
2、接口可以使得客户用同样的方式来处理相同接口的不同组件(这种能力,在C++中被称为多态能力)。
3、接口可以使内部具体实现得以隐藏。

4、如何用C++来实现接口?
下面的代码实现了一个简单的接口,其中组件CA实现了IX和IY接口: 
 
   
注:
1、  IX和IY是实现接口的纯抽象基类。
2、  纯抽象基类指的是仅包含纯抽象函数的基类。
3、  纯虚函数指的是用“=0”标记的虚函数。
4、  纯虚函数在派生类中必需实现。
5、  对纯虚函数的继承被称为接口继承。
6、  在本书中使用纯虚函数来实现所有的接口。
7、  CA是组件,IX、IY是接口,CA同时实现IX和IY两个接口。
8、  一个COM组件可以有多个接口。
9、  COM接口在C++中是使用纯抽象基类来实现的。
10、一个C++类可以使用多继承来实现一个可以提供多个接口的组件。
11、一个COM组件可以由多个C++类来完成,如可以再包含CFactory类。
12、一个COM组件也可以不包含任何C++类,如用C语言实现。

5、interface是关键字吗?
为了避免将接口定义成一个类的形式,微软在OBJBASE.H中定义如下:
#define interface struct
定义为struct的原因在于struct的成员将自动具有共有属性,因此无需加public造成不必要的混乱。

6、如何在客户端访问组件?
在客户端访问组件,主要是通过组件接口来访问,代码如下:


 
7、如何用图形来表示接口?
图形化表示接口如下:


 8、如何在C++控制台程序中具有MFC的TRACE输出能力?
可以自己定义自己的TRACE函数,如下:
void TRACE(const char * pMsg){cout<<pMsg<<endl;}

 9、什么是__stdcall和__cdecl调用约定?
 
__stdcall:标准调用约定
1、是对Microsoft编译器的一个扩充。任何一个支持支持开发win32程序的编译器都会有此选项或者与之等价的选项。
2、使用__stdcall标记的函数都使用标准调用约定。这些函数在返回到调用者之间将参数从栈中删除。
3、标准调用约定不支持带有变参的函数,如printf函数。
4、几乎所有的win32API函数(除了变参外)都使用标准调用约定,因为标准调用约定可减少代码的大小。
5、大多数编程语言也都使用标准约定,如VB缺省情况下使用标准调用约定。
6、Pascal调用约定等同于__stdcall调用约定。
7、COM接口所提供的所有函数使用的都是标准调用约定。
 
__cdecl:C调用约定
1、在常规的C/C++调用约定中,栈的清理工作是由调用者完成的。
2、带有变参的函数所使用的约定仍然是C调用约定。
 
参考:
1、微软在WINDEF.H中将Pascal定义为:
#define pascal __stdcall  
2、微软在OBJBASE.H定义STDMETHORDCALLTYPE如下:
#define STDMETHORDCALLTYPE __stdcall

10、本章示例程序存在哪些问题?
1、客户直接使用指向CA的指针:CA *pA=new CA;此举违背了接口原则。
2、使用new和delete等直接控制组件的生命期,
3、new和delete同时违背了组件的语言无关性,因为这都是c++特有的操作。
所以还必须在以后的章节中慢慢消除这些问题。

11、类和组件的关系?
1、类并非组件。
2、一个组件可以由一个或多个C++类实现。
3、一个组件也可以不用任何C++类实现,比如使用C语言实现的组件。

12、类与接口的关系?
1、一个类可以继承多个不同的接口来并实现之。
2、单个类实现单个接口。

13、如何解决接口名称冲突?
当一个组件包含有多个接口时,可能会发生名称冲突,一是接口名称之间的冲突,二是一个接口内部的函数与另外一个接口内部的函数名称的冲突。可以用下列方法来解决:
1、接口名称的冲突可以使用某种简单的约定进行有效的避免。如在接口名称前加公司名称或产品名称。如产品Xyz的IFly接口,可以使用IXyzFly接口名称。
2、函数名称的冲突亦可类似接口名称方法进行解决。如IXyzFly::Fly与IAbcFly::Fly,可以改为:IXyzFly::XyzFly与IAbcFly:: AbcFly
3、不使用多重接口继承。
4、使用包容与聚合技术。
注:COM接口是一个二进制内存标准,客户同接口的连接是通过接口在其表示的内存块中的位置完成的,而不是通过接口名称或其成员函数名称完成。因此COM并不关心接口的名称或其成员函数的名称。

 14、如何理解接口的不变性?
接口的不变性是指一旦接口被公布,那么它将永远保持不变。
1、对组件升级时,一般不会修改原有的接口,而是根据需要适当的增加一些新的接口来扩展其原有的功能。
2、通过使用多重接口的继承技术,使得这些组件不但能够支持原有的接口,还可以支持新的接口。因此多重继承为组件和客户可以智能对同对方的新版本进行交互打下了扎实的基础。

15、如何理解接口的多态性?
多态性是指可以按照同一种方式来处理不同的对象。
若两个不同的组件支持同一个接口,那么客户可以使用相同的代码来处理其中任何的一个组件,也就是说,客户可以使用相同的方式来处理不同的组件,从而实现了接口的多态性。
多重接口的支持能力为接口的多态提供了更多的机会,也使得多态的重要性更为突出。因为多态性可以使代码更加有效的被复用。

16、接口内存结构如何定义?     

 
上面所定义的纯抽象基类,实际上定义的是一个内存块结构。其定义如下图所示:

 
对内存块结构图的说明:
1、pIX指针:IX接口指针,指向vtbl指针。
2、vtbl指针:指向虚拟函数表的指针。
3、虚拟函数表存储的是指向各个成员函数的指针。
4、COM接口的内存结构同C++编译器为抽象类所生成的内存结构是相同的,因此从这个角度来说,IX既是一个COM接口也是一个抽象基类。
5、此内存结构仅仅只是定义,在派生类中实现此基类时才会真正被分配。
6、接口的内存结构在不同的操作系统上可能不同。

17、vtbl指针有何作用?
Vtbl指针在由pIX接口指针到虚拟函数表的过程中,看上去增加了一个额外的级别,有什么作用呢?由于C++编译器生成代码时,实现抽象基类的派生类可能会将特定的实例的信息(如成员数据)同vtbl一起保存。
 
假设类CA派生于IX,并拥有自己的成员数据m_dA。假设类CB也派生于IX;
 
单个实例时:pA=new CA ;
pA内存结构图如下(实例数据以黑色背景表示):

 

拥有两个实例时:pA1=new CA ;pA2=new CA;
两个实例有各自不同的vtbl指针及其数据成员空间(其实vtbl也可以看着是数据成员),但可以共享虚拟表。如下图:

 

不同的类,继承同一个接口,可以使用相同的虚拟表(vtbl)。
pA=new CA ;pB=new CB;
pIX=pA;//访问CA中的成员函数FXn
pIX=pB;//访问CB中的成员函数FXn
pIX->Fx1;//继承同一接口的所有的类均可被客户按同一种方式处理


 
注:
1、  上图的实例数据用空框表示,因为COM一般都不关心实例数据。
2、  两个虚拟表的结构相同,即两个表中的函数Fx i所占据的表格项都在第i项。

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

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/benkong2008/archive/2008/12/17/3537623.aspx

 

 

第3章 QueryInteface

=================================================================================
未经许可,也可转载,但请注明出处!希望能够与各位初学COM的网友共勉:-)

=======================================================================
 

本节提要:本章重点讲解了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实现的规则有哪些?
规则1、QureryInterface返回的总是同一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。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/benkong2008/archive/2008/12/18/3544093.aspx

 

未完,还想看看原帖吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值