COM原理及应用之可连接对象
1、COM的高级特性
COM规范中有一些高级特性,如可连接对象、永久存储、一致的数据传输等,使COM规范具有更强的生命力,它们也是OLE的基础,但它们的应用又不仅仅局限于OLE,这些高级特性已经广泛应用于Windows操作系统上。
2、入接口、出接口与接收器
入接口(incoming interface)是组件暴露给客户,被动地监听并为客户请求作出反应的接口。而出接口(outgoing interface)是指组件主动与客户进行通信的接口。
出接口不是由对象实现的,而是由客户程序来实现,客户实现这些接口,并把接口指针告诉对象,以后对象利用此接口指针与客户进行通信。在客户程序方,实现这些接口的对象被称为接收器(sink)。接收器本身也是一个COM对象,但它往往比较简单,只用于监听处理组件对象的通知或请求。
客户与对象之间的关系是相对的,入接口和出接口也是一个相对概念,它们只用于通信的一个方向。
3、可连接对象
如果一个COM对象支持一个或多个出接口,则称这样的对象为可连接对象(connectable object),或称为源对象(source)。
可连接对象的出接口也是COM接口,它包含一组成员函数,每个成员函数代表了一个事件(event)、一个通知(notification)或者一个请求(request)。
4、事件、通知与请求
事件和通知在概念上是完全一致的,只是用在不同的场合,例如在COM对象中当某个属性被改变时,它可以给客户发送一个通知;而当特定事情发生时,比如定时消息或用户鼠标操作发生时,对象产生一个事件,客户程序可以处理这些事件。然而,请求的概念则稍有不同,对象给客户发出请求,它希望客户能提供某些信息,期望客户能有应答。
从COM规范的意义上来讲,不管是事件、通知还是请求,它们都通过出接口的成员函数来实现。
5、客户与可连接对象的关系
虽然接收器也是一个COM,但它有特殊性,它位于客户程序内部,并不需要通过COM库来创建,所以接收器并不需要CLSID来标识,也不需要类厂,接收器的标识和创建过程完全是客户程序内部的事情。对于客户程序外部而言,接收器也是一个单独的COM对象,它有自己的引用计数,有自己的接口查询方法,即 QueryInterface成员函数。COM只要求接收器是一个独立的COM对象,COM规范对接收器的实现没有任何限制。
一般可连接对象不应该向接收器对象请求其他的接口,即不应该调用接收器的QueryInterface成员函数。接收器通常专用于某个出接口指针,接收器对象只实现该出接口,当然出接口是基接口(比如IUnknown)除外。
可连接对象和接收器可以形成一对多或者多对一的关系,也即客户与可连接对象之间可以是一对多或者多对一的关系。
6、可连接对象的基本结构
可连接对象可以支持一个或多个出接口,它通过接口IConnectionPointContainer管理所有的出接口。对应于每个出接口,可连接对象又管理了一个称为连接点(connection point)的对象,每一个连接点对象实现了IConnectionPoint接口,客户通过连接点对象建立接收器与可连接对象的连接。连接点即可以访问可连接对象的内部信息,也可以访问客户方的接收器,而其它可以直接使用可连接对象的引用计数器。
7、枚举器
在COM规范中,枚举器(Enumerator)只是一个概念,没有确定的接口用于规定枚举器的各项操作,这是因为枚举器所枚举的数据单元的类型不确定,所以也无法给出确切的定义。客户程序利用枚举器对COM对象中的数据单元进行枚举操作,枚举器把客户对数据单元的操作进行了标准化,因此,COM对象可以按照标准的方法把数据提供给客户,而不必建立二者之间新的协议。
因为枚举器对象是一个内部对象,它只需暴露枚举接口,不需要CLSID和类厂,所以枚举器对象的实现比较简单,只需实现枚举操作并控制好引用计数即可。
客户的接收器与源对象的连接点建立连接时,源对象使用连接点枚举器管理连接点对象,连接点对象又用连接枚举器管理连接,通过两层结构建立对象与接收器之间的连接。把源对象与连接点对象分开实现,可以使它们各自保持一定的独立性。源对象与接收器之间的连接具有很好的扩展性,而且连接点对象的独立性也使得COM可连接对象机制更具灵活性和广泛性。
8、接收器的实现
在C++语言中,用一般的类从接口类派生,然后分别实现接口成员函数即可。客户程序在建立连接之前,要先创建接收器对象,因为接收器是客户程序的内部对象,所以在C++语言中可以用new操作符创建接收器对象,然后用此连接器对象建立它与源对象之间的连接。
9、事件的激发和处理
实现事件和请求是可连接对象机制的主要目标。事件即可由源对象的入接口成员函数激发,也可以由用户的某些操作引起,还可以由其它对象或客户调用而引起。总之,在源对象执行过程中,根据需要都可以激发事件或者向客户发出请求,事件和请求在程序逻辑上完全一致。
由于事件或请求是在每个连接上进行的,只有建立了连接的接收器对象才会收到事件或请求。
连接点对象和接收器对象肯能位于不同的进程中,甚至在不同的机器环境中。因此,事件从激发到处理不一定是直接的函数调用,这是可连接对象机制与一般回调函数机制的重要不同。只有在单线程模型下,连接点对象才直接调用接收器对象的事件控制函数。从连接点对象到接收器对象之间的通信过程与以前讨论的客户和对象之间的通信过程一样,也符合COM线程模型规范,必要时侯也需要进行列集处理。
10、出接口通信连接的建立
首先客户方通过源对象的IConnectionPointContainer接口得到源对象的出接口IID,并进一步向源对象请求 IProvideClassInfo接口,调用IProvideClassInfo::GetClassInfo成员函数得到ITypeInfo结构,再进一步得到出接口的ITypeInfo结构而获取接口的所有类型信息,包括成员函数、函数参数的个数和参数类型等。类型信息是客户和源对象双方的通信协议标准。
根据出接口的类型信息在程序运行过程中实现动态接收器对象很不容易。虽然可连接对象提供了完善的双向通信机制,但客户要在运行过程中根据源对象的类型信息响应事件或请求并不容易。为此,OLE发展了COM的可连接对象的机制,它使用IDispatch接口作为出接口,利用IDispatch接口中方法(method)的分发功能实现事件控制函数。IDispatch接口的主要特点是,它可在运行时刻而不是在编译时刻把成员函数与特定的分发ID进行绑定操作,这种特性称为迟绑定(late binging)。IDispatch接口是Microsoft实现自动化技术的基础,现在已经得到了广泛的应用。
11、用IDispatch接口作为出接口
IDispatch接口是自动化对象的基本接口,在高级语言或者脚本语言中,可以直接用符号化的名字即字符串访问自动化对象的属性(property)和方法(method)。使用IDispatch接口有三方面的显著有点:第一,用名字访问属性和方法非常简单易用;第二,自动化对象的IDispatch 接口的vtable是固定的,在有些高级语言或脚本语言中没有指针数据类型,所以在这些语言中描述自定义接口比较困难;第三,IDispatch接口支持迟绑定特性,可以在运行过程中根据名字访问属性或方法。
COM已经提供了IDispatch接口的代理对象(proxy)和存根对象(stub),所以,使用IDispatch接口作为出接口可直接用于进程外源对象的出接口。
IDispatch接口把所有的调用都通过其成员函数Invoke来实现,并且它提供了管理属性和方法的分发ID机制,以及一套描述参数和返回值的方法,所以使得运行时刻动态绑定属性和方法并进行参数类型检查成为可能。可以说Invoke函数是自动化对象的命令翻译器。
根据不同的开发环境和运行环境,实现Invoke函数可以采用不同的方法。如果在编译时刻可以决定客户应该响应那些事件或请求,则可以在程序中建立一张表,把每个事件或请求的分发ID和对应的控制函数作为表项放到表中,把这张表称为事件映射表。MFC的COleControl类使用这种方法处理 ActiveX控制的事件和请求。
利用IDispatch接口作为出接口可以很好地解决接收器的动态创建过程。利用IDispatch接口作为源对象的出接口,由源对象提供出接口的类型信息,即事件控制函数的所有信息,客户程序根据这些类型信息,在Invoke函数中调用相应的事件控制函数。
12、MFC对连接和事件的支持
(1)MFC实现了连接点类CConnectionPoint,CConnectionPoint实现了IConnectionPoint接口,它用一个数组枚举器管理连接;
(2)CCmdTarget也提供了一组宏支持连接点对象;
(3)CCmdTarget类有一个内嵌的结构成员m_xConnPtContainer专门用于存放接口IConnectionPointContainer的vtable和偏移量;
(4)连接点是可连接对象的核心,但连接点的主要目的是激发事件或发送请求,因此,我们应该对每个事件或请求编写一个激发函数。
MFC提供了类COleDispatchDriver,他主要用于IDispatch接口的客户方调用操作,利用COleDispatchDriver的成员函数,客户可以创建自动化对象,也可以把COleDispatchDriver对象与某个自动化对象联系起来,更有意义的是,COleDispatchDriver使得IDispatch::Invoke调用的参数处理更为简单。
13、用CCmdTarget实现源对象的程序结构图
文章出处:http://www.diybl.com/course/3_program/c++/cppxl/20081118/151933.html