1 使用SafeArray
SafeArray是VB中的數組儲存方式。通過SafeArray,可以在VC++和VB間相互調用。SafeArray也是Automation中的標準數組儲存方式。
1.1 SafeArray處理函數
COM提供了一套API用於處理SafeArray。為了保證程式和SafeArray結構無關[1],程式中建立、讀取、更改和釋放SafeArray都應該通過這些API進行,而不應該直接讀寫SafeArray結構。
下面介紹常用的SafeArray處理函數。
建立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用於建立一維自訂類型或介面指標數組。
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兩種指標類型,如果在數組中放其它介面類型的指標,可能在跨套間使用中會出現問題。
讀取和寫入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. 訪問自訂結構數組
訪問自訂結構數組的時候,可以使用#iimport自動產生或者IDL編譯產生的類型定義。如果沒有辦法取得自訂結構的聲明,可以使用IRecordInfo介面中的方法間接訪問自訂結構。
首先需要取得自訂結構的長度,這可以通過IRecordInfo::GetSize方法取得。
訪問自訂結構中的欄位內容,通過IRecordInfo::GetField和IRecordInfo::PutField方法實現。
通過IRecordInfo中的其它方法還可以取得每個欄位的屬性內容。大家可以參考相關文檔。
釋放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參數傳遞數組中需要注意的地方。
輸入數組
對於輸入數組,可以使用VARIANT指標,也可以使用VARIANT型別參數。在這兩種情況下,VARIANT中的類型是不同的。
當使用VARIANT指標時,輸入的VARIANT參數的類型(vt參數的值)是VT_ARRAY | VT_BYREF | VT_xxx。此時,使用VARIANT參數的pparray欄位取得SafeArray指標。
如果參數是VARIANT,輸入的VARIANT參數的類型(vt參數的值)是VT_ARRAY | VT_xxx。使用VARIANT參數的parray欄位取得SafeArray指標。
必須注意這兩種情況下,VARIANT的類型不同,所以代碼也會有區別。
輸出數組
輸出和輸入輸出數組,必須使用VARIANT指標,這時,VARIANT類型是VT_ARRYA | VT_BYREF | VT_xxx。
1.5 SafeArray記憶體管理
使用COM專用的建立和銷毀API函數處理SafeArray。
對於輸入型的SafeArray,調用方負責建立和銷毀SafeArray;對於輸出型的SafeArray,由被呼叫者建立,調用方銷毀;輸入輸出型SafeArray,調用方建立,被呼叫者可以銷毀並重新建立,最終由調用方銷毀。