• [转]在COM中使用数组参数SafeArray


    [转]在COM中使用数组参数-SafeArray

    来源:http://www.7880.com/Info/Article-5208abe0.html

    1      使用SafeArray

    SafeArrayVB中的数组存储方式。通过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_UNKNOWNVT_DISPATCH时。pvExtra是一个指向IID(接口GUID)的指针。在目前的COM规范中,pvExtra只能是IID_IUnknownIID_IDispatch。并且必须和vt的取值一致。

    a.   创建自定义类型数组

    vtVT_RECORD时。pvExtra必须是一个IRecordInfo指针。绝大多数情况下,我们从TLB中取得自定义类型的IRecordInfo指针。以下是取得IRecordInfo的代码:

    IRecordInfo * pRecordInfo;

    hr = GetRecordInfoFromGuids(

    LibID,

    MajorVer,

    MinorVer,

    LOCALE_USER_DEFAULT,

        TypeGUID,

    &pRecordInfo);

    上述代码中,LibID是所TLBGUIDMajorVerMinorVer分别是TLB的主、次版本号,TypeGUID是自定义结构的GUID

    函数返回的是IRecordInfo接口的指针。

    b.   创建COM对象数组

    当需要创建COM数组时,可以使用IUnknown指针,也可以用IDispatch指针。如果需要使用其它指针类型,应该使用QueryInterface方法取得,而不能直接在数组中保存。因为SafeArray数组的序列化程序只能处理IUnknownIDispatch两种指针类型,如果在数组中放其它接口类型的指针,可能在跨套间使用中会出现问题。

    1.2.2            读取和写入SafeArray数组。

    读写SafeArray数组时。应该使用COM提供的标准APICOM提供了大量函数用于SafeArray数组的操作,本文中仅使用其中的两个函数,SafeArrayAccessDataSafeArrayUnaccessData,和一些辅助用的函数。实际上是用这两个函数就可以进行所有的数组操作了。其它的函数用于对单个元素的操作,由于使用不多,而且效率也不高,所以本文中不进行说明。

    1. SafeArrayAccessData

    这个函数用于获取SafeArray的数据指针,并锁定SafeArray数组的数据。在取得了数据指针之后,就可以直接访问SafeArray数组中的数据了。

    如果数组类型是Type,那么所取得的数据指针实际上就是Type类型的数组的地址。在多维数组的情况下,必须把多个维度的下标转换成一维下标进行访问。

    2. SafeArrayUnaccessData

    这个函数的作用是对SafeArray数据解锁,解锁后,就不应该继续对数据指针进行读写访问。如果要访问,必须重新获取并锁定数据。

    3. 确定数组结构

    在访问数组之前,必须知道数组中数据的类型,、维数以及每个维度的下界和长度。COM提供了取得这些数组参数的函数。

    取得类型,返回“VT_”开头的类型枚举值:

    HRESULT SafeArrayGetVartype (

        SAFEARRAY * pSA,

        VARTYPE * pVarType);

    取得维数,返回数组的维数:

    UINT SafeArrayGetDim (

        SAFEARRAY * pSA);

    取得每个维度的属性,返回指定维数(nDim)的上界和下界(nDim1开始):

    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个维度,每个维度的长度(上界减去下界加一)分别是L1L2Ln。要转换的下标是X1X2Xn。可以根据下述公式转换成一维数组的下标。

    X1+X2*L1+X3*(L1*L2)+X4*(L1*L2*L3)+…+Xn*(L1*L2*…*L(n-1))

    6. 访问自定义结构数组

    访问自定义结构数组的时候,可以使用#iimport自动生成或者IDL编译产生的类型定义。如果没有办法取得自定义结构的声明,可以使用IRecordInfo接口中的方法间接访问自定义结构。

    首先需要取得自定义结构的长度,这可以通过IRecordInfo::GetSize方法取得。

        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);  

     }

     就读到缓冲区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(long index=0;index<uIsRead;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是一个有趣的成员,它的结构不太直观。它是数据范围的数组。该数组的大小依safe array维数的不同而有所区别。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] 

    0

    rgsabound[1]

    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(),该功能释放该参数的计数。

  • 相关阅读:
    送给热爱书法的朋友们
    [原创]中秋随笔 祝大家中秋快乐
    Comsenz力邀您的加盟
    夜半冻醒有感
    Comsenz力邀您的加盟
    成熟的谷子先低头
    [转载]10个经典的web2.0配色方案网站
    无法嵌入互操作类型“Microsoft.Office.Interop.Excel.ApplicationClass”
    C#获取真实IP地址及分析
    使用TRY CATCH进行SQL Server异常处理
  • 原文地址:https://www.cnblogs.com/wubiyu/p/1401522.html
Copyright © 2020-2023  润新知