在COM中使用数组参数-SafeArray
关键字: DCOM 、数组、自定义类型、 Marshal 、 SafeArray 、 ICollection
1 使用SafeArray
SafeArray 是 VB 中的数组存储方式。通过 SafeArray ,可以在 VC++ 和 VB 间相互调用。 SafeArray 也是 Automation 中的标准数组存储方式。
1.1 SafeArray处理函数
COM 提供了一套 API 用于处理 SafeArray 。为了保证程序和 SafeArray 结构无关 [1] ,程序中建立、读取、更改和释放 SafeArray 都应该通过这些 API 进行,而不应该直接读写 SafeArray 结构。
下面介绍常用的 SafeArray 处理函数。
1.1.1 建立SafeArray
SAFEARRAY* SafeArrayCreate(
VARTYPE vt,
unsigned int cDims,
SAFEARRRAYBOUND * rgsabound
);
SAFEARRAY SafeArrayCreateEx(
VARTYPE vt,
unsigned int cDims,
SAFEARRRAYBOUND * rgsabound
PVOID pvExtra
);
SAFEARRAY* SafeArrayCreateVector(
VARTYPE vt,
long lLbound,
unsigned int cElements
);
SAFEARRAY* SafeArrayCreateVectorEx(
VARTYPE vt,
long lLbound,
unsigned int cElements,
LPVOID pvExtra
);
SafeArrayCreate 于建立多维普通数组。 SafeArrayCreateEx 用于建立多维自定义类型或接口指针数组。 SafeArrayCreateVector 用于建立一维普通数组。 SafeArrayCreateVectorEx 用于建立一维自定义类型或接口指针数组。
1.1.2 释放数组
HRESULT SafeArrayDestroy(
SAFEARRAY * psa
);
SafeArrayDestroy 用于释放创建的 SafeArray 数组。
1.1.3 访问数据
HRESULT SafeArrayAccessData(
SAFEARRAY * psa,
void HUGEP ** ppvData
);
HRESULT SafeArrayUnaccessData(
SAFEARRAY * psa
);
SafeArrayAccessData 函数返回数组的指针。而 SafeArrayUnaccessData 释放通过 SafeArrayAccessData 所取得的指针。
1.2 SafeArray相关处理
1.2.1 创建SafeArray数组
创建 SafeArray 可以使用 COM 提供的四个创建函数之一。所有的创建函数都返回一个 SafeArray 指针。通过这个指针可以读写 SafeArray 中的数据。 SafeArray 使用完后必须释放。
1. SafeArrayCreateVector
SAFEARRAY* SafeArrayCreateVector(
VARTYPE vt,
long lLbound,
unsigned int cElements
);
这个函数用来创建简单类型的一维数组。这个函数有三个参数: 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
SAFEARRAY* SafeArrayCreateVectorEx(
VARTYPE vt,
long lLbound,
unsigned int cElements,
LPVOID pvExtra
);
这个函数用于创建自定义类型或 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 的代码:
IRecordInfo * pRecordInfo;
hr = GetRecordInfoFromGuids(
LibID,
MajorVer,
MinorVer,
LOCALE_USER_DEFAULT,
TypeGUID,
&pRecordInfo);
上述代码中, LibID 是所 TLB 的 GUID , MajorVer 和 MinorVer 分别是 TLB 的主、次版本号, TypeGUID 是自定义结构的 GUID 。
函数返回的是 IRecordInfo 接口的指针。
b. 创建COM对象数组
当需要创建 COM 数组时,可以使用 IUnknown 指针,也可以用 IDispatch 指针。如果需要使用其它指针类型,应该使用 QueryInterface 方法取得,而不能直接在数组中保存。因为 SafeArray 数组的序列化程序只能处理 IUnknown 和 IDispatch 两种指针类型,如果在数组中放其它接口类型的指针,可能在跨套间使用中会出现问题。
1.2.2 读取和写入SafeArray数组。
读写 SafeArray 数组时。应该使用 COM 提供的标准 API 。 COM 提供了大量函数用于 SafeArray 数组的操作,本文中仅使用其中的两个函数, SafeArrayAccessData 和 SafeArrayUnaccessData ,和一些辅助用的函数。实际上是用这两个函数就可以进行所有的数组操作了。其它的函数用于对单个元素的操作,由于使用不多,而且效率也不高,所以本文中不进行说明。
1. SafeArrayAccessData
这个函数用于获取 SafeArray 的数据指针,并锁定 SafeArray 数组的数据。在取得了数据指针之后,就可以直接访问 SafeArray 数组中的数据了。
如果数组类型是 Type ,那么所取得的数据指针实际上就是 Type 类型的数组的地址。在多维数组的情况下,必须把多个维度的下标转换成一维下标进行访问。
2. SafeArrayUnaccessData
这个函数的作用是对 SafeArray 数据解锁,解锁后,就不应该继续对数据指针进行读写访问。如果要访问,必须重新获取并锁定数据。
3. 确定数组结构
在访问数组之前,必须知道数组中数据的类型,、维数以及每个维度的下界和长度。 COM 提供了取得这些数组参数的函数。
取得类型,返回“ VT_ ”开头的类型枚举值:
HRESULT SafeArrayGetVartype (
SAFEARRAY * pSA,
VARTYPE * pVarType);
取得维数,返回数组的维数:
UINT SafeArrayGetDim (
SAFEARRAY * pSA);
取得每个维度的属性,返回指定维数( nDim )的上界和下界( nDim 从 1 开始):
HRESULT SafeArrayGetLBound (
SAFEARRAY * pSA,
UINT nDim,
long * pLBound);
HRESULT SafeArrayGetUBound (
SAFEARRAY * pSA,
UINT nDim,
long * pUBound);
取得自定义类型接口,对于自定义结构数组,返回自定义结构类型数据的指针:
HRESULT SafeArrayGetRecordInfo (
SAFEARRAY * pSA,
IRecordInfo ** ppRecordInfo);
4. 访问普通一维数组
从 SafeArrayAccessData 返回的指针实际上就是 C 语言中的一维数组地址。在 VC++ 中可以像访问普通数组一样读写这个数组。
需要注意的是,在 C 语言中,所有的数组下标都是从 0 开始的。而在 SafeArray 中,数组下标可以从任何数字开始。所以在访问前必须进行转换。转换方法就是从 SafeArray 的下标中减去数组的下界,就可以得到 C 语言中数组的下标了。
如下:
Type * pData;
long LBound;
SafeArrayAccessData(pSA, (void HUGEP **) &pData);
SafeArrayGetLBound(pSA, 1, &LBound);
Type Item = pData[n – LBound];
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 方法取得。
访问自定义结构中的字段内容,通过 IRecordInfo::GetField 和 IRecordInfo::PutField 方法实现。
通过 IRecordInfo 中的其它方法还可以取得每个字段的属性内容。大家可以参考相关文档。
1.2.3 释放SafeArray数组
释放 SafeArray 数组应该通过 COM 的支持函数:
HRESULT SafeArrayDestroy(SAFEARRAY * pSA);
1.3 使用SafeArray的IDL定义
每个接口都要通过 IDL 生成代理和占位程序代码。为了使代理和占位程序能够正确地对参数进行序列化,必须正确的书写 IDL 定义。
MIDL 工具直接支持 SafeArray 类型数据的传递。但是,在传递 SafeArray 数据的时候,必须通过 SAFEARRAY 的指针进行。困难在于, VC++ 6.0 的添加方法和添加属性的工具不能够正确的处理 SafeArray 数组的情况。
在 IDL 中,数组必须指定类型,如下:
[id(10)] HRESULT Foo([in] SAFEARRAY(LONG) pParam);
在实现的函数声明中,要使用相应的指针类型:
HRESULT Foo(SAFEARRAY * pParam);
输出和输入输出类型的数组参数,在 IDL 中必须使用指针参数,而在函数声明中则是双重指针。
[id(11)] HRESULT Foo2([out] SAFEARRAY(LONG) * ppParam);
函数声明如下:
HRESULT Foo2(SAFEARRAY ** ppParam);
1.4 VARIANT和SafeArray
在 VB 的接口中,经常通过 VARIANT 传递数组参数。这里简述一下使用 VARIANT 参数传递数组中需要注意的地方。
1.4.1 输入数组
对于输入数组,可以使用 VARIANT 指针,也可以使用 VARIANT 类型参数。在这两种情况下, VARIANT 中的类型是不同的。
当使用 VARIANT 指针时,输入的 VARIANT 参数的类型 (vt 参数的值 ) 是 VT_ARRAY | VT_BYREF | VT_xxx 。此时,使用 VARIANT 参数的 pparray 字段取得 SafeArray 指针。
如果参数是 VARIANT ,输入的 VARIANT 参数的类型( vt 参数的值)是 VT_ARRAY | VT_xxx 。使用 VARIANT 参数的 parray 字段取得 SafeArray 指针。
必须注意这两种情况下, VARIANT 的类型不同,所以代码也会有区别。
1.4.2 输出数组
输出和输入输出数组,必须使用 VARIANT 指针,这时, VARIANT 类型是 VT_ARRYA | VT_BYREF | VT_xxx 。
1.5 SafeArray内存管理
使用 COM 专用的创建和销毁 API 函数处理 SafeArray 。
对于输入型的 SafeArray ,调用方负责创建和销毁 SafeArray ;对于输出型的 SafeArray ,由被调用方创建,调用方销毁;输入输出型 SafeArray ,调用方创建,被调用方可以销毁并重新创建,最终由调用方销毁。