SAFEARRAY

SAFEARRAYの概要
配列の確保
配列の確保(1次元配列専用)
配列の解放
配列のデータ操作
高速な配列データ操作
1要素の配列のデータ操作

 

SAFEARRAYの概要

SAFEARRAYとは、COMにおける配列データ型で、VBの配列そのままの機能を持っています。というよりもVBの配列そのもののようです。
CやJava言語の配列とは少し異なり、添え字における下限、上限を次元毎にユーザが指定出来るようになっています。
ここでは目的別に、SAFEARRAYを操作するC/C++コードと、配列を操作するVB/VBAコードを比較しつつ、紹介したいと思います。※1

 

配列の確保

配列の確保には、SafeArrayCreate API 及び、SAFEARRAYBOUND構造体を用います。
各要素の初期化は、SafeArrayCreate APIが行います。
動的に拡張可能な配列を作成する場合、以下のようなコードになります。

C/C++

//配列の各次元における添え字の下限、要素数を決定する
SAFEARRAYBOUND sabound[2]; //配列の次元毎に要素を用意する。ここでは、2次元配列。
sabound[0].cElements = 11; //1次元目の要素数
sabound[0].lLBound = 0;    //1次元目における添え字の下限値
sabound[1].cElements = 12; //2次元目の要素数
sabound[1].lLBound = 1;    //2次元目における添え字の下限値

//VT_I4は、32bit整数。VBでのLong。
SAFEARRAY* psa = SafeArrayCreate( VT_I4, 2, sabound);
if( psa == NULL) return E_OUTOFMEMORY;

VB/VBA

Dim sa() As Long
ReDim sa(0 To 10, 1 To 11) As Long

 

固定サイズの配列の場合は、さらにフラグをセットします。

C/C++

//配列の各次元における添え字の下限、要素数を決定する
SAFEARRAYBOUND sabound[2]; //配列の次元毎に要素を用意する。ここでは、2次元配列。
sabound[0].cElements = 11; //1次元目の要素数
sabound[0].lLBound = 0;     //1次元目における添え字の下限値
sabound[1].cElements = 12; //2次元目
sabound[1].lLBound = 1;     //...

//VT_I4は、32bit整数。VBでのLong。
SAFEARRAY* psa = SafeArrayCreate( VT_I4, 2, sabound);
if( psa == NULL) return E_OUTOFMEMORY;

psa->fFeatures = FADF_FIXEDSIZE;

VB/VBA

Dim sa(0 TO 10, 1 To 11) As Long

 

配列の確保(1次元配列専用)

1次元配列専用ですが、SAFEARRAYBOUNDを必要としないAPIも用意されています。
ちなみにMSDNを読むと、この関数で作成されたSAFEARRAYは、下限が0で、FADF_FIXEDSIZEフラグが常にセットされるとありますが、実際には、(ここで言う下限が配列の添え字だとすれば)下限は0以外にもなるし、フラグはセットされていない様子。

C/C++

//1から10の添え字を持つ一次元配列を作成する。
SAFEARRAY* psa = SafeArrayCreateVector( VT_I4, 1, 10);
if( psa == NULL) return E_OUTOFMEMORY;

VB/VBA

Dim sa() As Long
ReDim sa(1 To 10)

 

配列の解放

以下の通り。
配列要素がオブジェクトまたは、文字列である場合、APIはそれらの解放を適切に処理した後に配列を解放します。

C/C++

//1から10の添え字を持つ一次元配列を作成する。
SAFEARRAY* psa = SafeArrayCreateVector( VT_I4, 1, 10);
if( psa == NULL) return E_OUTOFMEMORY;
SafeArrayDestroy(psa);

VB

Dim sa() As Integer
ReDim sa(1 To 10)
Erase sa

 

配列のデータ操作

配列データは、C言語と同じように連続したメモリイメージによって維持されています。 以下では、SafeArrayPtrOfIndexを使用して要素位置から配列要素のポインターを取得し、操作する方法を紹介します。

SafeArrayPtrOfIndexを使用する前に、まずSafeArrayLockを呼び出してロックを確保してください。 また、操作終了後には、SafeArrayUnlockをコールして配列をアンロックしなければなりません。 ロックされたままだと、配列の伸張、解放等の際にエラーが返されます。

C/C++

//固定サイズ(10x10)の配列作成
SAFEARRAYBOUND sabound[2];
sabound[0].cElements = 10;
sabound[0].lLBound = 0;
sabound[1].cElements = 10;
sabound[1].lLBound = 0;
SAFEARRAY* psa = SafeArrayCreate( VT_I4, 2, sabound);
if( psa == NULL) return E_OUTOFMEMORY;

//配列のロック
HRESULT hr = SafeArrayLock(psa);
if( FAILED(hr) ) return hr;
 
//データの操作
long* pdata;
long indices[2];
 
//要素位置[0][1]への値セット
indices[0] = 0;
indices[1] = 1;
SafeArrayPtrOfIndex(psa, indices, (void HUGEP* FAR*)&pdata);
*pdata = 11;
 
//要素位置[1][2]への値セット
indices[0] = 1;
indices[1] = 2;
SafeArrayPtrOfIndex(psa, indices, (void HUGEP* FAR*)&pdata);
*pdata = 22;

//配列のアンロック
SafeArrayUnlock(psa);

VB/VBA

Dim sa() As Long
ReDim sa(0 To 9, 0 To 9)
sa(0, 1) = 11
sa(1, 2) = 22

 

高速な配列データ操作

SafeArrayAccessData APIは、高速な要素アクセスを行いたい場合に使用できます。
このAPIは、配列データへのポインタを返すのと同時に、SafeArrayへのロックを行います。
操作終了後にはもちろんアンロックをする必要があるので、SafeArrayUnaccessData APIをコールすること。

C/C++

//配列作成
SAFEARRAY* psa = SafeArrayCreateVector( VT_I4, 0, 2);
if( psa == NULL ) return E_OUTOFMEMORY;
 
//配列データへのポインタ取得(+Lock)
long* pdata;
HRESULT hr = SafeArrayAccessData(psa, 1, (void HUGEP* FAR*)&pdata);
if( FAILED(hr) ) return hr;
 
//データの操作
pdata[0] = 10;
pdata[1] = 11;

//UnLock
SafeArrayUnaccessData(psa);

VB/VBA

Dim sa() As Long
ReDim sa(0 To 1)
sa(0) = 10
sa(1) = 11

 

1要素の配列データ操作

SafeArrayPutElement/SafeArrayGetElementは、1要素を操作するためのAPIです。
SAFEARRAYへのロック/アンロックや配列要素がオブジェクトである場合の参照回数のインクリメント/デクリメント、が自動的に行われます。

C/C++

//配列作成
SAFEARRAYBOUND sabound[2];
sabound[0].cElements = 10;
sabound[0].lLBound = 0;
sabound[1].cElements = 10;
sabound[1].lLBound = 0;

SAFEARRAY* psa = SafeArrayCreate( VT_I4, 2, sabound);
if( psa == NULL ) return E_OUTOFMEMORY;

//要素位置[1][2]への値セット
long indices[] = { 1, 2 };
long data = 9;
HRESULT hr = SafeArrayPutElement(psa, indices, (void*)&data);
if( FAILED(hr) ) return hr;

VB/VBA

Dim sa() As Long
ReDim sa(0 To 9, 0 To 9)

sa(1, 2) = 9

 


※1:
VBScriptは型がVARIANTのみである以外は、大体おなじです。