ホーム < ゲームつくろー! < IKD備忘録

C++
SAFEARRAYを試してみる

(2009. 9. 8)


 SAFEARRAYなる物があります。名前にあるように安全な配列です。が、どう使うか良くわかりません。そこで調べてみる事にしました。

とりあえず参考になりそうな文章を備忘録(MSDN:Dr.GUI と COM オートメーション、第 3 部:続 COM のすばらしきデータ型):
http://msdn.microsoft.com/ja-jp/library/cc482694.aspx


@ そもそもSAFEARRAYとは?

 そもそもSAFEARRAYってなんだろうという話です。Visual Studioで宣言を見てみると、これは構造体でした:

typedef struct tagSAFEARRAY
{
    USHORT cDims;
    USHORT fFeatures;
    ULONG cbElements;
    ULONG cLocks;
    PVOID pvData;
    SAFEARRAYBOUND rgsabound[ 1 ];
} SAFEARRAY;

 それぞれの意味はMSDN(http://msdn.microsoft.com/en-us/library/ms221482.aspx)を見てみると・・・う〜ん英語か・・・。
cDimsは次元数のようです。
fFeaturesは配列のタイプは何なのか、どう配列が確保され、どうリリース(消去)されるべきかを表すフラグです。
cbElementsは配列の要素数です。
cLocksは配列をロックしている回数を表すようです。ロック中の配列は安全にアクセスできるものと思われます。
pvDataはデータへのポインタです。
rgsaboundは各次元の1境界とあるのですが、SAFEARRAYBOUNDを見ないとちょっと良くわかりません。

 おおよそニュアンスはわかりました。最後のSAFEARRAYBOUNDをさらに調べます:

typedef struct tagSAFEARRAYBOUND
{
    ULONG cElements;
    LONG lLbound;
} SAFEARRAYBOUND;

これもMSDNにあります(http://msdn.microsoft.com/en-us/library/ms221167.aspx):
lLboundは次元の下限(最初の要素番号)を表す数値が入るようです。なるほどです。
cElementsは配列の要素数です。

 なるほどこれは配列の先頭番号と要素数を表すだけのようです。VBなどは先頭を特定の番号から始められますものね。なるほど。これでSAFEARRAYそのものはわかりました。


A SAFEARRAYを扱う

 続いてSAFEARRAYを実際に扱ってみます。先の参考文章によるとSAFEARRAYを扱うには用意された関数を通すようです。MSDNによる関数一覧はこちら。掲載されている中で基本そうなものをざざっと知っておきます:

関数
役目
SafeArrayCreate
SafeArrayCreateVector
SafeArrayCopy
SafeArrayCreateEx
SafeArrayCreateVectorEx
配列生成
SafeArrayGetDim
SafeArrayGetLBound
SafeArrayGetUBound
SafeArrayGetElemsize
SafeArrayGetElement
SafeArrayPutElement
配列の次元情報の取得およびデータアクセス
SafeArrayLock
SafeArrayAccessData
SafeArrayPtrOfIndex
SafeArrayUnlock
SafeArrayUnaccessData
配列のロック・アンロック(安全なアクセス)
SafeArrayRedim 要素数変更
SafeArrayDestroy 配列の破棄

 詳しい事はさておき、SAFEARRAYを扱うには、上記関数を通して生成アクセス破棄するという処理が必要であるのがわかります。なるほど・・・。個々の関数について調べてみます。


○ SafeArrayCreate
http://msdn.microsoft.com/en-us/library/ms221234.aspx

SAFEARRAY* SafeArrayCreate(
    VARTYPE vt,
    unsigned int cDims,
    SAFEARRAYBOUND* rgsabound
);

 新規配列を生成。VARTYPEは配列の型を表すフラグのようです。cDimsは次元数。rgsaboundは先に示したように配列の下限番号と要素数です。これらを与えると引数に生成されたSAFEARRAYへのポインタが返ります。

 VARTYPEについて知る必要がありますね。プログラム上ではどう宣言されているのか・・・:

typedef unsigned short VARTYPE;

う〜まったく参考にならねぇ・・・(T_T)。仕方ないのでMSDNを見ると色々な型を表すフラグとして用意されているようです(http://msdn.microsoft.com/en-us/library/ms221170.aspx)。SAFEARRAYに使えるのは[V}と表示された中の次の項目のようです:

VT_I2
VT_I4
VT_R4
VT_R8
VT_CY
VT_DATE
VT_BSTR
VT_DISPATCH
VT_ERROR
VT_BOOL
VT_VARIANT
VT_DECIMAL
VT_RECORD
VT_UNKNOWN
VT_I1
VT_UI1
VT_UI2
VT_UI4
VT_INT
VT_UINT

色々ありますが、Iの添え字があるのはsigned integer、数字はバイト数のようです。RはReal型、CYは通貨、DATEは日付、BSTRは文字列、DISPATCHはIDispatchへのポインタ、ERRORはSCodes(?)、BOOLはBoolean型でtureなら-1、falseなら0とあります。VARIANTは何でも格納できるVARIANT型です。DECIMALは16バイトの固定小数点、RECORDはユーザー定義型だそうです。UNKNOWNは「わからない」ではなくIUnknownへのポインタ、つまりCOMインターフェイスです。UIはunsigned int型、INTとUINTは機種依存なint型及びUINT型になります。

 通常型を扱う分には上記の型で十分です。これでSAFEARRAYを作ることができそうです。


○ SafeArrayAccessData

HRESULT SafeArrayAccessData(
    SAFEARRAY* psa,
    void HUGEP** ppvData
);

配列要素へのアクセスを提供。psaにSafeArrayCreate関数で作った配列を渡すとppvDataに先頭ポインタが返る仕組みになっています。戻り値で先頭ポインタが有効か否かを判断できます。楽ちん。この関数を使うと配列がロックされるので、使い終わった時には必ずアンロックする必要があります。アンロックはSafeArrayUnaccessData関数が担ってくれます。


○ SafeArrayUnaccessData

HRESULT SafeArrayUnaccessData(
    SAFEARRAY *psa
);

ロックした配列をアンロック。psaにロック中の配列を渡しましょう。SafeArrayAccessData関数と必ず対になると思われます。

 最低限これらの関数でSAFEARRAYを扱えるようです。



B 極短テストプログラム

 一先ず使い方の見通しがついたので、テストプログラムを作ってみました。新規の配列を作って、要素に整数(unsigned int型)を入れ、出力して消すだけの極短プログラムです:

#include <windows.h>
#include <stdio.h>

int main(void)
{
    SAFEARRAY *pArray;
    SAFEARRAYBOUND rgb = {10, 0}; // 要素数10で先頭番号0の配列生成
    pArray = SafeArrayCreate( VT_UI4, 1, &rgb ); // SAFEARRAY作成

    unsigned int *p = 0;
    SafeArrayAccessData( pArray, (void**)&p ); // 配列の先頭ポインタを取得
    if ( p ) {
        // 値書き込み
        for ( unsigned i = 0; i < 10; i++ )
        p[i] = i * 100;
        SafeArrayUnaccessData( pArray ); // SAFEARRAYアンロック
    }

    // 出力してみます
    for ( long i = 0; i < 10; i++ ) {
        unsigned val = 0;
        SafeArrayGetElement( pArray, &i, (void**)&val ); // 要素へアクセスして値を格納してもらう
        printf("pArray[%d] = %d\n", i, val);
    }

    SafeArrayDestroy( pArray ); // 配列の解放

    return 0;
}

SafeArrayGetElement関数を使うと配列へ割りと楽にアクセスできます。


 SAFEARARAYは関数を通すと意外と簡単に使えるようです。この人はCOMでの安全な配列のやり取りに使われたりするので結構重要です。ん〜勉強になったです。