在COM中使用数组参数-概述
关键字:DCOM、数组、自定义类型、Marshal、SafeArray、ICollection
本文讲述在COM的接口中使用数组作为参数的三种方法。它们分别是:数组指针、SafeArray和ICollection。文章分析了各种方法的优缺点。本文的目的不是描述COM的基本原理和开发方法。为了能够更好的理解本文中的内容,读者需要具备基本的COM编程知识。
1 相关的基本概念
在COM中,如果对接口中方法的调用是跨套间的,就必须对所进行的调用进行序列化。序列化哪康氖鞘沟饔媚芄辉谡返奶准渲蟹⑸1疚牟淮蛩阆晗该枋鎏准浜托蛄谢睦砺郏龆砸婕暗降囊恍└拍罱兴得鳌?lt;/SPAN
1.1 套间(Apartment)
在一个进程中可以包含多个套间,每个套间可以包含一个或多个线程[1]。包含单个线程的套间叫做单线程套间;包含多个线程的套间叫做多线程套间。在一个进程中最多可以包含一个多线程套间,但可以拥有0或多个单线程套间。每个使用COM的线程,无论是客户程序还是COM程序都要通过调用CoInitialize或CoInitializeEx函数进入套间,同时确定所在套间的类型。在进入套间之前不能使用COM功能,否则会导致错误结果。
1.2 序列化(Marshal)
在COM架构中调用者和被调用者如果在不同的套间,就不可以直接调用,而必须通过代理(Proxy)和占位(Stub)程序调用。代理程序和调用者在同一个套间中,而占位程序和被调用者在同一个套间中。代理程序模仿COM组件的行为,接受调用,而占位程序模仿调用者的行为发出调用。这样就可以保证调用是在同一个套间中进行了。代理和占位程序之间通过特定的网络通讯协议传递被调用的方法、参数和返回值。把调用转换成网络协议的过程叫做序列化(Marshal)。由于涉及到指针或数组类型的传递地址型的参数,序列化过程非常复杂,幸好我们有简单的方法可以生成效率还算不错的代理和占位程序。
对于指针类型,具体的指针地址是不重要的,关键是指针指向的内存中的数据。所以在传递指针类型的参数时,必须传递所指向的数据。对于数组类型数据也有类似的情况。如果存在双重或多重指针,情况就会变得更加复杂。
例如long*类型的参数。在序列化的时候代理程序把参数所指向的长整型值传递给占位程序,占位程序要为参数申请内存,然后把长整型的值存放到申请到的内存中,使用这个新的内存地址作为参数调用目标函数。在函数返回的时候,内存中的数据变化被占位程序传递回代理程序。代理程序把数据复制回调用者的内存,然后返回。如果指针所指向的数据不是单个的值,而是一块不定大小的内存,序列化时就要确定所要传递数据的长度。另外,在多重指针的情况下,要传递的就不是一块数据,而可能是多块数据段了。
在后面的讨论中,我们会详细地说明如何生成正确的代理和占位程序。
1.3 代理(Proxy)和存根(Stub)
COM通过代理和存根实现序列化功能。每个可能跨套间调用的接口都必须有相应的代理和存根程序。代理和存根程序是在同一个动态连接库中的。每个COM接口的设计者负责实现自己的代理和存根程序。一般情况下,代理和存根程序的代码可以通过一个叫做MIDL的工具自动生成的,我们要做的只是把它编译出来。
代理和存根的工作方式如下图:
代理的作用是在客户套间中“伪装”成COM对象,供客户程序调用。而存根的作用则是在COM对象所在的套间中“伪装”成客户程序,发出调用请求。代理和存根之间通过网络协议[2]交换调用请求和返回结果。
2 概述
在COM中使用数组可以使用三种方法:数组指针、SafeArray和ICollection。数组指针和我们熟悉的C/C++程序中传递数组的方法是相同的、SafeArray是VB中标准的存放数组的方法,也是Automation中的标准方法、ICollection方法是通过一个独立的COM对象传递数据。这三种方法各有优缺点,应该按照具体的需求决定使用哪种方法。
2.1 数组指针
数组指针是标准的C/C++中的数组参数传递方式。数组指针实际上就是数组元素序列化存放时的首地址。数组指针的操作非常简单,所以也是效率最高的传递方式。但是,这种方式不能够在VB中使用。数组指针可以传递一维数组,也可以传递多维数组。如果COM的客户端是VC++程序的话,这是最好的传递方式。
数组指针作为跨套间的调用参数时,需要进行marshal。所以,应该编译和注册proxy/stub。
2.2 SafeArray
SafeArray是标准的VB数组存放方式。和数组指针类似,SafeArray可以传递一维数组,也可以传递多维数组。由于SafeArray具有比数组指针更复杂的结构,所以,编程比使用数组指针复杂[3],程序运行效率也相对较低。
使用SafeArray方式传递的数组,可以从VB程序中调用,也可以从VC++程序中调用。而且,由于SafeArray是Automation的标准数据,所以可以通过缺省的基于TLB的proxy/stub进行跨套间的调用,而不必编译和注册自己的proxy/stub。
2.3 ICollection
ICollection方式是最复杂,也是使用最广泛的。ICollection并不是一个接口的名称,而是指实现了枚举器和索引属性的IDispatch接口。这种数组传递方式的特点是自己实现数组对象,所以有最大的灵活性,可以实现按需生成数组元素等高级功能。
ICollection所传递的数组对象不再是普通的指针或特定的结构,而是一个独立的COM对象。由于传递的是接口,所以参数具有面向对象的多态性特征,就是说数组元素可以是自己实现的,也可以是其他人实现的,只要是实现了有特定属性的IDispatch接口就可以作为参数。另一方面,由于用作数组的COM对象可以单独设计,所以,可以使用更加合理的实现方式,例如使用列表、hash表或平衡树等方式实现。
有的数组实现,只需要访问少数的几个元素,或者元素个数理论上是无穷的,或者每个数组元素的生成需要耗费大量的资源。这时,应该使用ICollection方式实现数组传递。
3 各种方法的比较
数组指针
SafeArray
ICollection
代码量
少
较少
大
兼容Automation
不兼容
兼容
兼容
开发工具
VC++
VC++/VB
VC++
可重用性
低
低
高
按需生成元素
不可
不可
可
模型设计难度
容易
容易
难
程序运行速度
快
较慢
根据实际情况分析
分布式应用
RPC
RPC
RPC或对象复制
设计灵活性
尚可
尚可
高
[1] 最新的COM标准中,某些线程可能会跨越套间,这里不讨论这种情况。
[2] 实际上基于在远程过程调用协议(RPC)的一种协议。
[3] 指用VC++编程,如果用VB实现COM,这是唯一的传递方式。