-
SafeArray处理函数
1 使用SafeArray
SafeArray是VB中的数组存储方式。通过SafeArray,可以在VC++和VB间相互调用。SafeArray也是Automation中的标准数组存储方式。
1.1 SafeArray处理函数
COM提供了一套API用于处理SafeArray。为了保证程序和SafeArray结构无关,程序中建立、读取、更改和释放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. 访问自定义结构数组
访问自定义结构数组的时候,可以使用#import自动生成或者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 在ADO编程中经常使用。它的主要目的是用于automation中的数组型参数的传递。因为在网络环境中,数组是不能直接传递的,而必须将其包装成 SafeArray。实质上SafeArray就是将通常的数组增加一个描述符,说明其维数、长度、边界、元素类型等信息。SafeArray也并不单独使用,而是将其再包装到VARIANT类型的变量中,然后才作为参数传送出去。在VARIANT的vt成员的值如果包含VT_ARRAY|...,那么它所封装的就是一个SafeArray,它的parray成员即是指向SafeArray的指针。SafeArray中元素的类型可以是VARIANT能封装的任何类型,包括VARIANT类型本身。
使用SafeArray的具体步骤:
方法一:
包装一个SafeArray:
(1)定义变量,如:
VARIANT varChunk;
SAFEARRAY *psa;
SAFEARRAYBOUND rgsabound[1];
(2) 创建SafeArray描述符:
//read array from a file.
uIsRead=f.Read(bVal,ChunkSize);
if(uIsRead==0)
break;
rgsabound[0].cElements = uIsRead;
rgsabound[0].lLbound = 0;
psa = SafeArrayCreate(VT_UI1,1,rgsabound);
(3)放置数据元素到SafeArray:
for(long index=0;index<uIsRead;index++)
{
if(FAILED(SafeArrayPutElement(psa,&index,&bVal[index])))
::MessageBox(NULL,"出毛病了。","提示",MB_OK | MB_ICONWARNING);
}
一个一个地放,挺麻烦的。
(4)封装到VARIANT内:
varChunk.vt = VT_ARRAY|VT_UI1;
varChunk.parray = psa;
这样就可以将varChunk作为参数传送出去了。
读取SafeArray中的数据的步骤:
(1)用SafeArrayGetElement一个一个地读
BYTE buf[lIsRead];
for(long index=0; index<lIsRead; index++)
{
::SafeArrayGetElement(varChunk.parray,&index,buf+index);
}
vb教程就读到缓冲区buf里了。
方法二:
使用SafeArrayAccessData直接读写SafeArray的缓冲区:
(1) vb教程读缓冲区:
BYTE *buf;
SafeArrayAccessData(varChunk.parray, (void **)&buf);
f.Write(buf,lIsRead);
SafeArrayUnaccessData(varChunk.parray);
(2)写缓冲区:
BYTE *buf;
::SafeArrayAccessData(psa, (void **)&buf);
for(long index=0;index<uIsRead;index++)
{
buf[index]=bVal[index];
}
::SafeArrayUnaccessData(psa);
varChunk.vt = VT_ARRAY|VT_UI1;
varChunk.parray = psa;
vb教程这种方法读写SafeArray都可以,它直接操纵SafeArray的数据缓冲区,比用SafeArrayGetElement和 SafeArrayPutElement速度快。特别适合于读取数据。但用完之后不要忘了调用::SafeArrayUnaccessData (psa),否则会出错的。
以下就是SAFEARRAY的Win32定义:
typedef struct tagSAFEARRAY
{
unsigned short cDims;
unsigned short fFeatures;
unsigned long cbElements;
unsigned long cLocks;
void * pvData;
SAFEARRAYBOUND rgsabound[ 1 ];
} SAFEARRAY;
vb教程这个结构的成员(cDims,cLocks等)是通过API函数来设置和管理的。真正的数据存放在pvData成员中,而SAFEARRAYBOUND结构定义该数组结构的细节。以下就是该结构成员的简要描述:
成员 描述
cDims 数组的维数
fFeatures 用来描述数组如何分配和如何被释放的标志
cbElements 数组元素的大小
cLocks 一个计数器,用来跟踪该数组被锁定的次数
pvData 指向数据缓冲的指针
rgsabound 描述数组每维的数组结构,该数组的大小是可变的
rgsabound是一个有趣的成员,它的结构不太直观。它是数据范围的数组。该数组的大小依safearray维数的不同而有所区别。rgsabound成员是一个SAFEARRAYBOUND结构的数组--每个元素代表SAFEARRAY的一个维。
typedef struct tagSAFEARRAYBOUND
{
unsigned long cElements;
unsigned long lLbound;
} SAFEARRAYBOUND;
维数被定义在cDims成员中。例如,一个\'C\'类数组的维数可以是[3][4][5]-一个三维的数组。如果我们使用一个SAFEARRAY来表示这个结构,我们定义一个有三个元素的rgsabound数组--一个代表一维。
cDims = 3;
...
SAFEARRAYBOUND rgsabound[3];
rgsabound[0]元素定义第一维。在这个例子中ILBOUND元素为0,是数组的下界。cElements成员的值等于三。数组的第二维 ([4])可以被rgsabound结构的第二个元素定义。下界也可以是0,元素的个数是4, vb教程第三维也是这样。
要注意,由于这是一个"C"数组,因此由0 开始,对于其它语言,例如Visual Basic,或者使用一个不同的开始。该数组的详细情况如下所示:
元素 cElements ILbound
rgsabound[0] 3 0
rgsabound[1] 4 0
rgsabound[2] 5 0
关于SAFEARRAYBOUND结构其实还有很多没说的。我们将要使用的SAFEARRAY只是一个简单的单维字节数组。我们通过API函数创建数组的时候,SAFEARRAYBOUND将会被自动设置。只有在你需要使用复杂的多维数组的时候,你才需要操作这个结构。
还有一个名字为cLocks的成员变量。很明显,它与时间没有任何的关系--它是一个锁的计数器。该参数是用来控制访问数组数据的。在你访问它之前,你必须锁定数据。通过跟踪该计数器,系统可以在不需要该数组时安全地删除它。
创建SAFEARRAY
创建一个单维SAFEARRAY的简单方法是通过使用SafeArrayCreateVector API函数。该函数可分配一个特定大小的连续内存块。
SAFEARRAY *psa;
// create a safe array to store the stream data
// llen is the number of bytes in the array.
psa = SafeArrayCreateVector( VT_UI1, 0, llen );
SafeArrayCreateVector API创建一个SAFEARRAY,并且返回一个指向它的指针。首个参数用来定义数组的类型--它可以是任何有效的变量数据类型。为了传送一个串行化的对 象,我们将使用最基本的类型--一个非负的字节数组。VT--UI1代表非负整形的变量类型,1个字节。
常数\'0\'定义数组的下界;在C++中,通常为0。最后的参数llen定义数组元素的个数。在我们的例子中,这与我们将要传送对象的字节数是一样的。我们还没有提数组大小(llen)是怎样来的,这将在我们重新考查串行化时提及。
在你访问SAFEARRAY数据之前,你必须调用SafeArrayAccessData。该函数锁定数据并且返回一个指针。在这里,锁定数组意味着增加该数组的内部计数器(cLocks)。
// define a pointer to a byte array
unsigned char *pData = NULL;
SafeArrayAccessData( psa, (void**)&pData );
... use the safe array
SafeArrayUnaccessData(psa);
相应用来释放数据的函数是SafeArrayUnaccessData(),该功能释放该参数的计数。
定义COM接口
在COM中传送一个SAFEARRAY是很简单的,你需要设置自己的接口来传送SAFEARRAY结构。SAFEARRAY是一个本地的IDL数据类型。以下就是一个接口要处理的IDL代码:
[
object,
uuid(EEC6D3EF-32F7-11D3-9EA1-00105A132526),
dual,
helpstring("IBlobData Interface"),
pointer_default(unique)
]
interface IBlobData : IUnknown
{
HRESULT GetArray([out] SAFEARRAY(unsigned char) *pData);
HRESULT SetArray([in] SAFEARRAY(unsigned char) pData );
};
vb教程只要你定义它包含的数据类型,SAFEARRAY就是一个有效的数据类型。语句SAFEARRAY(unsigned char)可用来传送任何类型的二进制数据。"Unsigned char"意味着该数据将会是二进制字节,它与VT_UI1变量类型相对应。
它的两个方法是相对的--GetArray方法从服务器得到一个对象。SetArray方法则发送一个对象给服务器。我们将不会谈及为该接口创建一个COM对象的基本问题。这个工作可通过使用ATL向导来完成。
接下来,我们会将串行化和SAFEARRAY两部分的知识结合起来,讲述一个例子。
Fig1.0数据类型允许用在IDispatch接口上。以下这些数据类型是可被一个类库调用的。
类型 名字 描述
byte VT_UI1 非负字节
Short VT_I2 有符号16位短整型
Long VT_I4 有符号32位长整型
float VT_R4 一个IEEE 4字节实型数字
double VT_R8 一个IEEE 8字节实型数字
VARIANT_BOOL VT_BOOL 16位布尔 0=false, 0xFFFF=true
SCODE VT_ERROR 16位错误码
CY VT_CY 16位货币结构
DATE VT_DATE 使用双精度数字表示的日期
BSTR VT_BSTR visual basic风格的字符结构
DECIMAL VT_DECIMAL 一个十进制的结构
IUnknown VT_UNKNOWN 一个COM接口的指针
IDispatch VT_DISPATCH COM Dispatch接口的指针
SAFEARRAY * VT_ARRAY 一个用作传送数组数据的特别结构
VARIANT * VT_VARIANT 一个VARIANT结构的指针
void * 普通的指针
VT_BYREF 任何类型(除指针外)的指针
SafeArray是VB中的数组存储方式。通过SafeArray,可以在VC++和VB间相互调用。SafeArray也是Automation中的标准数组存储方式。
1.1 SafeArray处理函数
COM提供了一套API用于处理SafeArray。为了保证程序和SafeArray结构无关,程序中建立、读取、更改和释放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. 访问自定义结构数组
访问自定义结构数组的时候,可以使用#import自动生成或者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 在ADO编程中经常使用。它的主要目的是用于automation中的数组型参数的传递。因为在网络环境中,数组是不能直接传递的,而必须将其包装成 SafeArray。实质上SafeArray就是将通常的数组增加一个描述符,说明其维数、长度、边界、元素类型等信息。SafeArray也并不单独使用,而是将其再包装到VARIANT类型的变量中,然后才作为参数传送出去。在VARIANT的vt成员的值如果包含VT_ARRAY|...,那么它所封装的就是一个SafeArray,它的parray成员即是指向SafeArray的指针。SafeArray中元素的类型可以是VARIANT能封装的任何类型,包括VARIANT类型本身。
使用SafeArray的具体步骤:
方法一:
包装一个SafeArray:
(1)定义变量,如:
VARIANT varChunk;
SAFEARRAY *psa;
SAFEARRAYBOUND rgsabound[1];
(2) 创建SafeArray描述符:
//read array from a file.
uIsRead=f.Read(bVal,ChunkSize);
if(uIsRead==0)
break;
rgsabound[0].cElements = uIsRead;
rgsabound[0].lLbound = 0;
psa = SafeArrayCreate(VT_UI1,1,rgsabound);
(3)放置数据元素到SafeArray:
for(long index=0;index<uIsRead;index++)
{
if(FAILED(SafeArrayPutElement(psa,&index,&bVal[index])))
::MessageBox(NULL,"出毛病了。","提示",MB_OK | MB_ICONWARNING);
}
一个一个地放,挺麻烦的。
(4)封装到VARIANT内:
varChunk.vt = VT_ARRAY|VT_UI1;
varChunk.parray = psa;
这样就可以将varChunk作为参数传送出去了。
读取SafeArray中的数据的步骤:
(1)用SafeArrayGetElement一个一个地读
BYTE buf[lIsRead];
for(long index=0; index<lIsRead; index++)
{
::SafeArrayGetElement(varChunk.parray,&index,buf+index);
}
vb教程就读到缓冲区buf里了。
方法二:
使用SafeArrayAccessData直接读写SafeArray的缓冲区:
(1) vb教程读缓冲区:
BYTE *buf;
SafeArrayAccessData(varChunk.parray, (void **)&buf);
f.Write(buf,lIsRead);
SafeArrayUnaccessData(varChunk.parray);
(2)写缓冲区:
BYTE *buf;
::SafeArrayAccessData(psa, (void **)&buf);
for(long index=0;index<uIsRead;index++)
{
buf[index]=bVal[index];
}
::SafeArrayUnaccessData(psa);
varChunk.vt = VT_ARRAY|VT_UI1;
varChunk.parray = psa;
vb教程这种方法读写SafeArray都可以,它直接操纵SafeArray的数据缓冲区,比用SafeArrayGetElement和 SafeArrayPutElement速度快。特别适合于读取数据。但用完之后不要忘了调用::SafeArrayUnaccessData (psa),否则会出错的。
以下就是SAFEARRAY的Win32定义:
typedef struct tagSAFEARRAY
{
unsigned short cDims;
unsigned short fFeatures;
unsigned long cbElements;
unsigned long cLocks;
void * pvData;
SAFEARRAYBOUND rgsabound[ 1 ];
} SAFEARRAY;
vb教程这个结构的成员(cDims,cLocks等)是通过API函数来设置和管理的。真正的数据存放在pvData成员中,而SAFEARRAYBOUND结构定义该数组结构的细节。以下就是该结构成员的简要描述:
成员 描述
cDims 数组的维数
fFeatures 用来描述数组如何分配和如何被释放的标志
cbElements 数组元素的大小
cLocks 一个计数器,用来跟踪该数组被锁定的次数
pvData 指向数据缓冲的指针
rgsabound 描述数组每维的数组结构,该数组的大小是可变的
rgsabound是一个有趣的成员,它的结构不太直观。它是数据范围的数组。该数组的大小依safearray维数的不同而有所区别。rgsabound成员是一个SAFEARRAYBOUND结构的数组--每个元素代表SAFEARRAY的一个维。
typedef struct tagSAFEARRAYBOUND
{
unsigned long cElements;
unsigned long lLbound;
} SAFEARRAYBOUND;
维数被定义在cDims成员中。例如,一个\'C\'类数组的维数可以是[3][4][5]-一个三维的数组。如果我们使用一个SAFEARRAY来表示这个结构,我们定义一个有三个元素的rgsabound数组--一个代表一维。
cDims = 3;
...
SAFEARRAYBOUND rgsabound[3];
rgsabound[0]元素定义第一维。在这个例子中ILBOUND元素为0,是数组的下界。cElements成员的值等于三。数组的第二维 ([4])可以被rgsabound结构的第二个元素定义。下界也可以是0,元素的个数是4, vb教程第三维也是这样。
要注意,由于这是一个"C"数组,因此由0 开始,对于其它语言,例如Visual Basic,或者使用一个不同的开始。该数组的详细情况如下所示:
元素 cElements ILbound
rgsabound[0] 3 0
rgsabound[1] 4 0
rgsabound[2] 5 0
关于SAFEARRAYBOUND结构其实还有很多没说的。我们将要使用的SAFEARRAY只是一个简单的单维字节数组。我们通过API函数创建数组的时候,SAFEARRAYBOUND将会被自动设置。只有在你需要使用复杂的多维数组的时候,你才需要操作这个结构。
还有一个名字为cLocks的成员变量。很明显,它与时间没有任何的关系--它是一个锁的计数器。该参数是用来控制访问数组数据的。在你访问它之前,你必须锁定数据。通过跟踪该计数器,系统可以在不需要该数组时安全地删除它。
创建SAFEARRAY
创建一个单维SAFEARRAY的简单方法是通过使用SafeArrayCreateVector API函数。该函数可分配一个特定大小的连续内存块。
SAFEARRAY *psa;
// create a safe array to store the stream data
// llen is the number of bytes in the array.
psa = SafeArrayCreateVector( VT_UI1, 0, llen );
SafeArrayCreateVector API创建一个SAFEARRAY,并且返回一个指向它的指针。首个参数用来定义数组的类型--它可以是任何有效的变量数据类型。为了传送一个串行化的对 象,我们将使用最基本的类型--一个非负的字节数组。VT--UI1代表非负整形的变量类型,1个字节。
常数\'0\'定义数组的下界;在C++中,通常为0。最后的参数llen定义数组元素的个数。在我们的例子中,这与我们将要传送对象的字节数是一样的。我们还没有提数组大小(llen)是怎样来的,这将在我们重新考查串行化时提及。
在你访问SAFEARRAY数据之前,你必须调用SafeArrayAccessData。该函数锁定数据并且返回一个指针。在这里,锁定数组意味着增加该数组的内部计数器(cLocks)。
// define a pointer to a byte array
unsigned char *pData = NULL;
SafeArrayAccessData( psa, (void**)&pData );
... use the safe array
SafeArrayUnaccessData(psa);
相应用来释放数据的函数是SafeArrayUnaccessData(),该功能释放该参数的计数。
定义COM接口
在COM中传送一个SAFEARRAY是很简单的,你需要设置自己的接口来传送SAFEARRAY结构。SAFEARRAY是一个本地的IDL数据类型。以下就是一个接口要处理的IDL代码:
[
object,
uuid(EEC6D3EF-32F7-11D3-9EA1-00105A132526),
dual,
helpstring("IBlobData Interface"),
pointer_default(unique)
]
interface IBlobData : IUnknown
{
HRESULT GetArray([out] SAFEARRAY(unsigned char) *pData);
HRESULT SetArray([in] SAFEARRAY(unsigned char) pData );
};
vb教程只要你定义它包含的数据类型,SAFEARRAY就是一个有效的数据类型。语句SAFEARRAY(unsigned char)可用来传送任何类型的二进制数据。"Unsigned char"意味着该数据将会是二进制字节,它与VT_UI1变量类型相对应。
它的两个方法是相对的--GetArray方法从服务器得到一个对象。SetArray方法则发送一个对象给服务器。我们将不会谈及为该接口创建一个COM对象的基本问题。这个工作可通过使用ATL向导来完成。
接下来,我们会将串行化和SAFEARRAY两部分的知识结合起来,讲述一个例子。
Fig1.0数据类型允许用在IDispatch接口上。以下这些数据类型是可被一个类库调用的。
类型 名字 描述
byte VT_UI1 非负字节
Short VT_I2 有符号16位短整型
Long VT_I4 有符号32位长整型
float VT_R4 一个IEEE 4字节实型数字
double VT_R8 一个IEEE 8字节实型数字
VARIANT_BOOL VT_BOOL 16位布尔 0=false, 0xFFFF=true
SCODE VT_ERROR 16位错误码
CY VT_CY 16位货币结构
DATE VT_DATE 使用双精度数字表示的日期
BSTR VT_BSTR visual basic风格的字符结构
DECIMAL VT_DECIMAL 一个十进制的结构
IUnknown VT_UNKNOWN 一个COM接口的指针
IDispatch VT_DISPATCH COM Dispatch接口的指针
SAFEARRAY * VT_ARRAY 一个用作传送数组数据的特别结构
VARIANT * VT_VARIANT 一个VARIANT结构的指针
void * 普通的指针
VT_BYREF 任何类型(除指针外)的指针
最新更新
Objective-C语法之代码块(block)的使用
VB.NET eBook
Add-in and Automation Development In VB.NET 2003 (F
Add-in and Automation Development In VB.NET 2003 (8
Add-in and Automation Development in VB.NET 2003 (6
Add-in and Automation Development In VB.NET 2003 (5
AddIn Automation Development In VB.NET 2003 (4)
AddIn And Automation Development In VB.NET 2003 (2)
Addin and Automation Development In VB.NET 2003 (3)
AddIn And Automation Development In VB.NET 2003 (1)
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
SQL Server -- 解决存储过程传入参数作为s
武装你的WEBAPI-OData入门
武装你的WEBAPI-OData便捷查询
武装你的WEBAPI-OData分页查询
武装你的WEBAPI-OData资源更新Delta
5. 武装你的WEBAPI-OData使用Endpoint 05-09
武装你的WEBAPI-OData之API版本管理
武装你的WEBAPI-OData常见问题
武装你的WEBAPI-OData聚合查询
OData WebAPI实践-OData与EDM
OData WebAPI实践-Non-EDM模式