1. 程式人生 > >關於SAFEARRAY的,轉載了篇文章,比較全

關於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 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<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(longindex=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是一個有趣的成員,它的結構不太直觀。它是資料範圍的陣列。該陣列的大小依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        任何型別(除指標外)的指標