有个问题,64位COM组件调用32位COM组件(进程外)的时候:
32位组件里引入_IDTExtensibility2接口,64调用的时候失败;
如果不用引入的方法,而把接口函数写到IDL里,则调用成功。
比较奇怪。
(关键是接口函数有SAFEARRAY**类型的参数,让系统默认转就失败。
返回的错误时加载DLL模块失败,其实是代理存根调用有异常,可能转
SAFEARRAY的时候有异常,但最最原始的根源还不能确定)
--------------------------------------------------------------------------------------------
注意 safearray 做参数,在idl里写比较特殊。以下这篇博客比较全。
http://blog.sina.com.cn/s/blog_72ce76690100qkde.html
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 intcDims,
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,
UINTnDim,
long *pLBound);
HRESULT SafeArrayGetUBound (
SAFEARRAY* pSA,
UINTnDim,
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;
SAFEARRAYBOUNDrgsabound[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(longindex=0;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 { ::SafeArrayGetElement(varChunk.parray,&index,buf+index); } 就读到缓冲区buf里了。 方法二: 使用SafeArrayAccessData直接读写SafeArray的缓冲区: (1)读缓冲区: BYTE *buf; SafeArrayAccessData(varChunk.parray, (void**)&buf); f.Write(buf,lIsRead); SafeArrayUnaccessData(varChunk.parray); (2)写缓冲区: BYTE *buf; ::SafeArrayAccessData(psa, (void**)&buf); for(longindex=0;index { buf[index]=bVal[index]; } ::SafeArrayUnaccessData(psa); varChunk.vt = VT_ARRAY|VT_UI1; varChunk.parray = psa; 这种方法读写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; 这个结构的成员(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,第三维也是这样。 要注意,由于这是一个"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的简单方法是通过使用SafeArrayCreateVectorAPI函数。该函数可分配一个特定大小的连续内存块。 SAFEARRAY *psa; // create a safe array to store the streamdata // llen is the number of bytes in thearray. psa = SafeArrayCreateVector( VT_UI1, 0, llen ); SafeArrayCreateVectorAPI创建一个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 ); }; 只要你定义它包含的数据类型,SAFEARRAY就是一个有效的数据类型。语句SAFEARRAY(unsignedchar)可用来传送任何类型的二进制数据。"Unsignedchar"意味着该数据将会是二进制字节,它与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 任何类型(除指针外)的指针
【转载完成】