關於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 任何型別(除指標外)的指標