关键字:DCOM、数组、自定义类型、Marshal、SafeArray、ICollection
本文讲述在COM的接口中使用数组作为参数的三种方法。它们分别是:数组指针、SafeArray和ICollection。文章分析了各种方法的优缺点。本文的目的不是描述COM的基本原理和开发方法。为了能够更好的理解本文中的内容,读者需要具备基本的COM编程知识。
1相关的基本概念
在COM中,如果对接口中方法的调用是跨套间的,就必须对所进行的调用进行序列化。
1.1套间(Apartment)
在一个进程中可以包含多个套间,每个套间可以包含一个或多个线程(最新的COM标准中,某些线程可能会跨越套间,这里不讨论这种情况。)。包含单个线程的套间叫做单线程套间;包含多个线程的套间叫做多线程套间。在一个进程中最多可以包含一个多线程套间,但可以拥有0或多个单线程套间。每个使用COM的线程,无论是客户程序还是COM程序都要通过调用CoInitialize或CoInitializeEx函数进入套间,同时确定所在套间的类型。在进入套间之前不能使用COM功能,否则会导致错误结果。
1.2序列化(Marshal)
在COM架构中调用者和被调用者如果在不同的套间,就不可以直接调用,而必须通过代理(Proxy)和占位(Stub)程序调用。代理程序和调用者在同一个套间中,而占位程序和被调用者在同一个套间中。代理程序模仿COM组件的行为,接受调用,而占位程序模仿调用者的行为发出调用。这样就可以保证调用是在同一个套间中进行了。代理和占位程序之间通过特定的网络通讯协议传递被调用的方法、参数和返回值。把调用转换成网络协议的过程叫做序列化(Marshal)。由于涉及到指针或数组类型的传递地址型的参数,序列化过程非常复杂,幸好我们有简单的方法可以生成效率还算不错的代理和占位程序。
对于指针类型,具体的指针地址是不重要的,关键是指针指向的内存中的数据。所以在传递指针类型的参数时,必须传递所指向的数据。对于数组类型数据也有类似的情况。如果存在双重或多重指针,情况就会变得更加复杂。
例如long*类型的参数。在序列化的时候代理程序把参数所指向的长整型值传递给占位程序,占位程序要为参数申请内存,然后把长整型的值存放到申请到的内存中,使用这个新的内存地址作为参数调用目标函数。在函数返回的时候,内存中的数据变化被占位程序传递回代理程序。代理程序把数据复制回调用者的内存,然后返回。如果指针所指向的数据不是单个的值,而是一块不定大小的内存,序列化时就要确定所要传递数据的长度。另外,在多重指针的情况下,要传递的就不是一块数据,而可能是多块数据段了。
在后面的讨论中,我们会详细地说明如何生成正确的代理和占位程序。
1.3代理(Proxy)和存根(Stub)
COM通过代理和存根实现序列化功能。每个可能跨套间调用的接口都必须有相应的代理和存根程序。代理和存根程序是在同一个动态连接库中的。每个COM接口的设计者负责实现自己的代理和存根程序。一般情况下,代理和存根程序的代码可以通过一个叫做MIDL的工具自动生成的,我们要做的只是把它编译出来。
代理和存根的工作方式如下图:
代理的作用是在客户套间中“伪装”成COM对象,供客户程序调用。而存根的作用则是在COM对象所在的套间中“伪装”成客户程序,发出调用请求。代理和存根之间通过网络协议(实际上基于在远程过程调用协议(RPC)的一种协议。)交换调用请求和返回结果。
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具有比数组指针更复杂的结构,所以,编程比使用数组指针复杂(指用VC++编程,如果用VB实现COM,这是唯一的传递方式。),程序运行效率也相对较低。
使用SafeArray方式传递的数组,可以从VB程序中调用,也可以从VC++程序中调用。而且,由于SafeArray是Automation的标准数据,所以可以通过缺省的基于TLB的proxy/stub进行跨套间的调用,而不必编译和注册自己的proxy/stub。
2.3 ICollection
ICollection方式是最复杂,也是使用最广泛的。ICollection并不是一个接口的名称,而是指实现了枚举器和索引属性的IDispatch接口。这种数组传递方式的特点是自己实现数组对象,所以有最大的灵活性,可以实现按需生成数组元素等高级功能。
ICollection所传递的数组对象不再是普通的指针或特定的结构,而是一个独立的COM对象。由于传递的是接口,所以参数具有面向对象的多态性特征,就是说数组元素可以是自己实现的,也可以是其他人实现的,只要是实现了有特定属性的IDispatch接口就可以作为参数。另一方面,由于用作数组的COM对象可以单独设计,所以,可以使用更加合理的实现方式,例如使用列表、hash表或平衡树等方式实现。
有的数组实现,只需要访问少数的几个元素,或者元素个数理论上是无穷的,或者每个数组元素的生成需要耗费大量的资源。这时,应该使用ICollection方式实现数组传递。
2.4 各种方法的比较
|
数组指针 |
SafeArray |
ICollection |
代码量 |
少 |
较少 |
大 |
兼容Automation |
不兼容 |
兼容 |
兼容 |
开发工具 |
VC++ |
VC++/VB |
VC++ |
可重用性 |
低 |
低 |
高 |
按需生成元素 |
不可 |
不可 |
可 |
模型设计难度 |
容易 |
容易 |
难 |
程序运行速度 |
快 |
较慢 |
根据实际情况分析 |
分布式应用 |
RPC |
RPC |
RPC或对象复制 |
设计灵活性 |
尚可 |
尚可 |
高 |
3使用数组指针
数组指针使用标准的C/C++数组表示方式。数组中的每个元素按照顺序在内存中依次排放。数组的下标从0开始计算。数组的第一个元素(下标为0的元素)的地址就是数组的指针,数组中每个元素所占的内存空间大小必须是固定的,只和数组类型有关。计算数组中某个元素的指针时,使用元素所占的字节数乘上元素下标就可以得出这个元素和数组指针之间的偏移量(准确地说,数组元素所占内存的字节数和编译时的对齐参数有关)。根据这个偏移量就可以计算出这个元素的地址指针了。
使用数组指针最重要的是确定数组长度,使序列化程序可以正确地复制内存。为了生成代理和存根程序,我们要使用接口定义语言(IDL)描述接口和COM对象类型。MIDL读取IDL文件中的描述,生成TLB和代理存根的代码。
3.1IDL声明
3.1.1参数传递方向
为了尽量提高序列化的效率,在IDL中可以确定参数的传递方向。参数的传递方向有三种:输入型(in)、输出型(out)、和输入输出型(in,out)。输入型参数从调用者传递到被调用者,被调用者对输入型参数的更改不传回调用者。输出型参数正相反,从被调用者分回调用者,而被调用者不关心参数的初始值。输入输出型参数在调用的时候传到被调用者,同时,被调用者可以对参数进行修改,这个修改在调用返回的时候会复制回调用者。
非指针类型一定是输入型参数。输出型参数和输入输出型参数一定是指针类型。
3.1.2数组长度和复制长度
在IDL声明中,应该正确的设置数组长度和复制长度。数组长度用size_is或min_is、max_is属性定义,复制长度则使用length_is或first_is、last_is属性定义。在目前的IDL实现中,min_is并没有实现,所以,min_is只能是0。
size_is属性用来设置数组在内存中的长度。数组在内存中的长度也可以通过max_is和min_is共同定义。他们的关系是:size = max – min + 1。由于min只能是0,所以,size = max + 1。
length_is属性用来设置在序列化时需要复制的元素数量。需要复制的元素数量也可以通过first_is和last_is共同定义。他们的关系是:length = last – first + 1。可以同时定义first_is和length_is来定义复制的范围,也可以同时定义first_is和last_is来定义复制的范围。如果只定义了first_is,则last_is和有效的max_is值相同。如果没有定义first_is,使用缺省值0。但是,length_is和last_is不可以同时定义。复制元素的范围不能够超出数组本身的范围。如果没有指定复制范围,缺省的复制范围是整个数组。
复制范围一般不在单纯的输入参数或输出参数上使用,而只在输入输出型参数中使用(复制范围还大量使用在自定义类型中的数组成员,这已经超出了本文的范围,请大家参考相关资料)。在IDL中指定复制范围会影响到哪部份的内存数据会从客户端复制到COM端;当COM端的方法返回时,也是根据复制范围,把数据复制回客户程序。数组从客户端复制到COM端和从COM端复制回客户端的范围可以是不同的。
数组用作输出参数时,有两种方案:调用方建立数组和被调用方建立数组。当输出数组的长度是可预知的时候,应该使用调用方建立数组的方式。数组用作输出时,需要把属性中的in改成out。参见例2和例3。
以下是使用数组指针的例子。
3.1.3双重指针
在IDL中,可以使用双重或多重指针类型的参数。这里只介绍用于数组输出用的双重指针,关于多重指针的使用,请参考相关文档。
双重指针的size_is属性中包含用逗号分隔的两个长度值,分别是顶级指针数组的长度和次级指针的长度。在长度值空缺的情况下,使用缺省值1。例如:size_is(m,n)的含义是顶级指针长度是m,次级指针长度是n。size_is(,n)相当于size_is(1,n)。
在使用数组指针传递数组时,常用的是顶级指针长度是1的双重指针。也就是指向数组指针的指针。
3.1.4被调用方建立数组
如果输出的数组长度无法预知,就需要被调用的函数动态申请内存,建立数组。这个时候,由于调用方不知道数组的长度,不可能事先申请内存。所以,需要被调用方申请内存,并且把所申请的内存的地址传给调用方。也就是说,调用方要传递存放数组指针地址的地址给被调用方,也就是通过数组指针的指针传递参数。
3.1.5多维数组
在IDL中,没有定义多维数组,如果要使用多维数组,必须转换成一维数组来处理。一维数组的长度是多维数组中每个维度长度的乘积。比如说3*5的数组可以用一个长度为15的数组表示。
3.1.6字符串
字符串是一种特殊的数组形式,这种数组不定义长度,而是以特殊标志——数值0结尾。由于在C语言中字符串都是以0结尾的方式存储,所以这种类型常被用于传递字符串参数。在IDL中规定字符串的元素类型只能是单字节或wchar_t类型,而且不能是多维的。
定义字符串的属性是string。字符串数组只能是一维的。如果字符串是输出参数,应该使用双重指针,或者使用size_is属性指定buffer的长度。
3.1.7固定长度数组
在上面提到的数组指针方法中,size_is中的参数也可以是常量,但是效率比较低。如果数组长度是一个常量,可以使用固定长度数组的定义。固定长度的数组可以是多维的,可以是输入、输出或输入输出类型。
3.2数组指针的内存管理
根据COM规范,输入型参数由调用方申请和释放内存。输出型参数由被调用方申请内存,由调用方释放内存。输入和输出型参数,由调用方申请内存,被调用方可以释放并重新申请内存,最终由调用方释放内存。
由于涉及到代理和存根,跨套间的调用时代理和存根也参与内存管理,所以,COM和客户端必须使用相同的内存管理方式。在COM中,系统提供了一套内存管理函数,凡是涉及到COM接口参数的内存块都必须通过这几些函数进行管理,这里列出这些函数原型,具体说明请参考相关文档。
4使用SafeArray
SafeArray是VB中的数组存储方式。通过SafeArray,可以在VC++和VB间相互调用。SafeArray也是Automation中的标准数组存储方式。
4.1SafeArray处理函数
COM提供了一套API用于处理SafeArray。为了保证程序和SafeArray结构无关[1],程序中建立、读取、更改和释放SafeArray都应该通过这些API进行,而不应该直接读写SafeArray结构。
下面介绍常用的SafeArray处理函数。
4.1.1建立SafeArray
SafeArrayCreate于建立多维普通数组。SafeArrayCreateEx用于建立多维自定义类型或接口指针数组。SafeArrayCreateVector用于建立一维普通数组。SafeArrayCreateVectorEx用于建立一维自定义类型或接口指针数组。
4.1.2释放数组
SafeArrayDestroy用于释放创建的SafeArray数组。
4.1.3访问数据
SafeArrayAccessData函数返回数组的指针。而SafeArrayUnaccessData释放通过SafeArrayAccessData所取得的指针。
4.2 SafeArray相关处理
4.2.1创建SafeArray数组
创建SafeArray可以使用COM提供的四个创建函数之一。所有的创建函数都返回一个SafeArray指针。通过这个指针可以读写SafeArray中的数据。SafeArray使用完后必须释放。
1. SafeArrayCreateVector
这个函数用来创建简单类型的一维数组。这个函数有三个参数:vt是数组类型、lLbound是数组下界值(最小下标)和数组长度。vt的取值如下表:
vt值 |
类型 |
VT_UI1 |
无符号1字节整数(BYTE)数组 |
VT_UI2 |
无符号2字节整数(WORD)数组 |
VT_UI4 |
无符号4字节整数(DWORD)数组 |
VT_UINT |
无符号整数(UINT)数组 |
VT_INT |
有符号整数(INT)数组 |
VT_I1 |
有符号1字节整数数组 |
VT_I2 |
有符号2字节整数数组 |
VT_I4 |
有符号4字节整数数组 |
VT_R4 |
IEEE 4字节浮点数(float)数组 |
VT_R8 |
IEEE 8字节浮点数(double)数组 |
VT_CY |
8字节定点数货币值数组 |
VT_BSTR |
VB字符串数组 |
VT_DECIMAL |
12字节定点数(大数字)数组 |
VT_ERROR |
标准错误编号数组 |
VT_BOOL |
布尔值数组 |
VT_DATE |
日期型数组 |
VT_VARIANT |
VB Variant类型数组 |
lLbound是数组的最小下标,可以是取负数。cElements是数组的长度。数组的最大下标的值是最小下标加上数组长度减一。
SafeArrayCreateVector函数返回SafeArray结构的指针。
2. SafeArrayCreateVectorEx
这个函数用于创建自定义类型或COM对象的SafeArray数组。和SafeArrayCreateVector类似,SafeArrayCreateVector也有类型、下界和长度的三个参数。SafeArrayCreateVectorEx还增加了一个参数pvExtra。
pvExtra的含义和vt的取值有关。当vt的取值在上表中的时候,pvExtra的取值没有作用。当vt取值VT_RECORD时,SafeArrayCreateVectorEx返回一个自定义类型(结构structure或联合union)的数组。这时,pvExtra必须是一个指向IRecordInfo的指针。
当vt取值是VT_UNKNOWN或VT_DISPATCH时。pvExtra是一个指向IID(接口GUID)的指针。在目前的COM规范中,pvExtra只能是IID_IUnknown和IID_IDispatch。并且必须和vt的取值一致。
a. 创建自定义类型数组
当vt是VT_RECORD时。pvExtra必须是一个IRecordInfo指针。绝大多数情况下,我们从TLB中取得自定义类型的IRecordInfo指针。以下是取得IRecordInfo的代码:
上述代码中,LibID是所TLB的GUID,MajorVer和MinorVer分别是TLB的主、次版本号,TypeGUID是自定义结构的GUID。
函数返回的是IRecordInfo接口的指针。
b. 创建COM对象数组
当需要创建COM数组时,可以使用IUnknown指针,也可以用IDispatch指针。如果需要使用其它指针类型,应该使用QueryInterface方法取得,而不能直接在数组中保存。因为SafeArray数组的序列化程序只能处理IUnknown和IDispatch两种指针类型,如果在数组中放其它接口类型的指针,可能在跨套间使用中会出现问题。
4.2.2读取和写入SafeArray数组。
读写SafeArray数组时。应该使用COM提供的标准API。COM提供了大量函数用于SafeArray数组的操作,本文中仅使用其中的两个函数,SafeArrayAccessData和SafeArrayUnaccessData,和一些辅助用的函数。实际上是用这两个函数就可以进行所有的数组操作了。其它的函数用于对单个元素的操作,由于使用不多,而且效率也不高,所以本文中不进行说明。
1. SafeArrayAccessData
这个函数用于获取SafeArray的数据指针,并锁定SafeArray数组的数据。在取得了数据指针之后,就可以直接访问SafeArray数组中的数据了。
如果数组类型是Type,那么所取得的数据指针实际上就是Type类型的数组的地址。在多维数组的情况下,必须把多个维度的下标转换成一维下标进行访问。
2. SafeArrayUnaccessData
这个函数的作用是对SafeArray数据解锁,解锁后,就不应该继续对数据指针进行读写访问。如果要访问,必须重新获取并锁定数据。
3. 确定数组结构
在访问数组之前,必须知道数组中数据的类型,、维数以及每个维度的下界和长度。COM提供了取得这些数组参数的函数。
4. 访问普通一维数组
从SafeArrayAccessData返回的指针实际上就是C语言中的一维数组地址。在VC++中可以像访问普通数组一样读写这个数组。
需要注意的是,在C语言中,所有的数组下标都是从0开始的。而在SafeArray中,数组下标可以从任何数字开始。所以在访问前必须进行转换。转换方法就是从SafeArray的下标中减去数组的下界,就可以得到C语言中数组的下标了。
如下:
5. 访问多维数组
访问多维数组和访问一维数组类似,只是要把多维下标转换成一维下标。把多维下标转换成一维下标的方法和在数组指针中介绍的是相似的。
设:有n个维度,每个维度的长度(上界减去下界加一)分别是L1、L2、…、Ln。要转换的下标是X1、X2、…、Xn。可以根据下述公式转换成一维数组的下标。
X1+X2*L1+X3*(L1*L2)+X4*(L1*L2*L3)+…+Xn*(L1*L2*…*L(n-1))
6. 访问自定义结构数组
访问自定义结构数组的时候,可以使用#iimport自动生成或者IDL编译产生的类型定义。如果没有办法取得自定义结构的声明,可以使用IRecordInfo接口中的方法间接访问自定义结构。
首先需要取得自定义结构的长度,这可以通过IRecordInfo::GetSize方法取得。
4.3 使用CComSafeArray封装SAFEARRAY
使用CComSafeArray的一个最大的好处,就是它会自动释放元素是VARIANT和BSTR。也就是说,如果你的类型是VARIANT,它会自动调用::VariantClear()。如果你的类型是BSTR,他会自动调用::SysStringFree()方法。
CComSafeArray可以包含以下类型:
vt值 |
类型 |
VT_UI1 |
无符号1字节整数(BYTE) |
VT_UI2 |
无符号2字节整数(WORD) |
VT_UI4 |
无符号4字节整数(ulong) |
VT_UI8 | 无符号8字节整数(ulonglong) |
VT_I1 |
有符号1字节整数 |
VT_I2 |
有符号2字节整数 |
VT_I4 |
有符号4字节整数 |
VT_I8 | 有符号8字节整数 |
VT_R4 |
IEEE 4字节浮点数(float) |
VT_R8 |
IEEE 8字节浮点数(double) |
VT_DECIMAL |
12字节定点数(大数字) |
VT_VARIANT |
VB Variant类型 |
只有下面几种类型完全支持: VT_BSTR,VT_VARIANT,VT_UNKNOWN,and VT_DISPATCH. 对于COM对象数组,可以调用QueryInterface转换为VT_UNKNOWN或VT_DISPATCH类型,再进行封装。
5使用ICollection
ICollection是从 IDispatch继承的接口。ICollection还需要一个IEnumVARIANT接口配合实现功能。IEnumVARIANT是从IUnknown继承的,而不是从IDispatch接口继承。
ICollection接口提供了最大的面向对象的设计灵活性和可重用性。在数组指针和SafeArray方法中,数组的每个元素必须事先计算出来,并且保存在特定的数据结构中。使用ICollection接口,可以设计出动态生成的数组,就是说数组的元素在需要的时候才进行计算,以便减少内存使用并加快处理速度。
5.1ICollection和IEnumVARIANT
ICollection接口用于定义数组对象,而IEnumVARIANT接口用于定义枚举对象。枚举对象的作用是按顺序读取数组元素,有时,通过枚举对象可以获得更高的效率。
ICollection和IEnumVARIANT的定义如下:
有的时候,COM对象不但要实现数组功能,而且还要实现其它功能。所以,大多数时候,COM对象实现的接口是从ICollection继承来的。
通过ICollection操纵数组大体上有两种方法。一种是通过Item属性用数组下标取得元素。这种方式,每次只能取得一个元素,而且要传递下标对象,所以效率比较低下。另一种方法是通过枚举器。数组对象的枚举器通过_NewEnum属性取得。通过枚举器只能按顺序获取元素,但每次可以取得任意多的元素,所以效率较高。ICollection对象可以只实现其中的一种访问方法,也可以两种都实现。ICollection中还有一个重要属性:Count。Count属性返回数组的长度,对于无法确定长度的数组,也可以不实现Count属性。
IEnumVARIANT接口用于定义枚举器。枚举器用于顺序读取数组元素。通过Next方法,可以一次读取任意多的元素。由于枚举器只可以按顺序访问数组元素,所以Next方法不需要传递下标。Skip方法用于跳过若干元素,而不读取。Reset把当前元素设置到数组头,这样就可以重新开始枚举。Clone用于获得一个新的枚举器。两个枚举器可以互不干扰的工作。
要注意的事,可能有某些数组对象的实现方法使用不同的属性名称。实际上ICollection中的属性名称是不重要的,重要的是Dispatch ID。只要通过Dispatch ID就可以取得正确的属性。
5.2数组对象
数组对象是实现了ICollection接口的COM对象。数组对象的使用者通过ICollection接口取得数组中的数据,而完全不需要知道数组的具体实现方式。这种设计的好处是使用数组的代码可以完全不理会数组的实现方法,而当数组的实现发生变化时,使用数组的代码可以在二进制代码上保持兼容,也就是说目标代码不用编译就可以使用。
最简单制作数组对象的方法是使用ATL的模板。CComEnumOnSTL模板用于生成实现IEnumVARIANT接口的枚举对象。当然,如果要实现数组对象的所有优点,最好自己编写数组对象的代码。
5.3ICollection参数的IDL声明
在IDL声明中。数组对象应该声明成IDispatch *。如果是输出或输入输出参数,则应该使用双重指针。
[id(0)] GetNumber([out] IDispatch ** ppObj);
[id(1)] SetNumber([in] IDispatch * pObj);
目前,我们看到的ICollection数组都是只读的。实际上ICollection完全可以设计成可读写的数组对象,只要把ICollection的Item属性设置成可读写的就可以了。关于可读写的ICollection对象请参考相关资料。
5.4通过ATL实现数组对象
ATL通过两个模板实现对ICollection的支持。它们就是CComEnumOnSTL和ICollectionOnSTLImpl。CComEnumOnSTL用于实现基于STL对象的枚举器。ICollectionOnSTLImpl用于实现ICollection接口。下面详细描述这两个模板的功能和用法。
5.4.1CComEnumOnSTL
CComEnumOnSTL的定义如下:
模板参数中,Base是枚举器所实现的接口,通常是IEnumVARIANT。piid是枚举器接口的IID,通常是IID_IEnumVARIANT。T是枚举器输出数值的类型,通常是VARIANT。Copy是复制类,用于将STL对象中的值转换成枚举器输出参数。CollType是用于存储数据的STL类型。ThreadModel是线程模式参数,可以是CComSingleThreadModel或CcomMultiThreadModel,缺省值是当前缺省的线程模式。
假设使用vector类保存数组元素。而vector参数是long型数据。可以通过以下方法实现枚举器。
1. 定义CollType
2. 定义Copy类
Copy类用于在STL类的元素类型和枚举器类型之间进行参数转换。每个Copy类必须有三个静态函数:init、copy、destroy。Init用于初始化枚举器类、copy用于把STL元素复制到枚举器参数、destroy用于销毁枚举器参数。
下面是用于在long和VARIANT之间转换的Copy类实例。
3. 定义枚举器
通过以上定义的类就可以方便的定义枚举器类型了。
5.4.2ICollectionOnSTLImpl
ICollectionOnSTLImpl用于帮助实现ICollection接口。ICollectionOnSTLImpl定义如下:
在ICollectionOnSTLImpl模板中,T是要实现的接口,一般会使用从ICollection继承的接口。CollType参数是用于保存数据的STL类型,这个类型应该和枚举器中的相同。ItemType是ICollection中Item属性的类型,一般是VARIANT。CopyItem是Item属性的Copy类,和枚举器中的Copy类是相同的。EnumType是枚举器的类型。
可以通过以下步骤实现ICollection接口。
1. 定义ICollection类型
2. 定义数组对象
定义数组对象和定义普通ATL的COM对象是类似的。只要把IDispatchImpl中的接口参数(第一个参数)变成刚刚完成的ICollectionOnSTLImpl参数就可以了。
5.5使用数组对象
对于通用的ICollection对象,只能够通过IDispatch访问。也就是说通过IDispatch::Invoke方法访问数组中的元素。
另一方面,ICollection对象通常指通过VARIANT类型传递数据。所以,我们也必须了解如何访问VARIANT类型的变量。
5.5.1调用IDispatch
IDispatch是Automation中定义的接口。通过IDispatch,COM客户可以取得接口中每个方法和属性的类型、参数和返回值等信息。通过IDispatch的Invoke方法,COM客户还可以直接调用接口中的方法和属性。IDispatch的内容非常丰富,这里不可能做全面地介绍,所以指对如何通过Invoke方法调用IDispatch做一个简单的说明。
1. Invoke方法的定义
5.5.2使用IEnumVARIANT枚举数据
要使用IEnumVARIANT枚举数据,首先必须取得IEnumVARIANT指针。取得IEnumVARIANT指针是通过ICollection的_NewEnum属性。具体操作可以参考上一节关于Invoke的说明。
在取得了IEnumVARIANT之后,就可以通过IEnumVARIANT顺序读取数组元素了。
请参考以下代码枚举数据:这段代码是将数组中的元素相加求总和。
原文链接:https://www.f2er.com/vb/261165.html