SAFEARRAY構造体




SAFEARRAYとは

主にCOM経由でのデータのやりとりを行う際に使われる。
VBの配列に似た性質を持っていて、各次元毎に配列の長さが決められたりする。
各要素をVARIANT型で持てば、かなり柔軟な情報格納ができる。
…、けど使うのはなんとなく煩雑な気がするし、正直めんどい。
COMを通じてExcelデータの取得、指定レンジオブジェクトへのデータの書き込みなどを
行う際には使わざるを得なくなる。
まぁVBのデータ型を持ってこれる配列、と認識しておいてもいいのでは?



宣言されてるところ

パス
c:\Program Files\Microsoft SDKs\Windows\v6.0A\Include\
        OAIdl.h
        OleAuto.h

※パス部分はローカル環境に依存。ちなみに上記はVisualStudio2008をProgram Filesにインストールした場合。



メンバ

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

cDims
配列の次元
fFeatures
SafeArrayルーチンによって使われるフラグ。SafeArrayXXXX()系の関数内部で参照される
cbElements
配列の要素のサイズ。ポインタが指すデータサイズは含まない。おそらくVARIANT型の要素を幾つ持っているか的なサイズ。
cLocks
対応するUnlockなしでのLockされた回数。Lockネストの深さだと思われ。
pvData
データへのVoid型ポインタ
rgsabound
各次元への境界(次元ごとに持ってると思われ)。
おそらくリニアに持っている配列データの区切り位置を指すのでは。

rgsaboundメンバは一番左の次元(例えばa[2][3][4]なら[2]の部分)がrgsabound[0]に格納され、一番右の次元がrgsabound[cDims-1]に格納される。
もし配列がC文法的に [2][5]と指定されていたら、配列は2つの要素を持つrgsaboundを擁する。
要素0は lLbound=0, cElements=2, 要素1はlLbound=0, cElements=5となる

fFeaturesフラグは配列がどのように解放されるかに影響する記述部分。
配列の解放については配列が持っているVARIANT要素の参照は無視して行うことができる。
指定できるフラグについては以下のとおり。

#define FADF_AUTO 0x0001
配列はスタック上に割り当てられる
#define FADF_STATIC 0x0002
配列は静的に割り当てられる
#define FADF_EMBEDDED 0x0004
配列は構造体に組み込まれる
#define FADF_FIXEDSIZE 0x0010
配列はリサイズ、再割り当てできない
#define FADF_BSTR 0x0100
BSTR型の配列
#define FADF_UNKNOWN 0x0200
IUnknown*型の配列
#define FADF_DISPATCH 0x0400
IDispatch*型の配列
#define FADF_VARIANT 0x0800
VARIANT型の配列
#define FADF_RESERVED 0xF0E8
このビットは将来のために予約済み

以上を見ても分かるとおり、COM用の配列という香りがプンプンしとる



生成時のあれこれ

SAFEARRAYの生成には以下の関数を使う

  • SafeArrayCreate()
  • SafeArrayCreateEx()
  • SafeArrayCreateVector()
  • SafeArrayCreateVectorEx()
  • SafeArrayCopy()

Vector系の生成関数は1次元配列の生成。いちいち次元情報(SAFEARAYBOUND)をセットしないでいいのでラク。
もちろん多次元配列には使えない。
EX系の生成関数はほとんど情報なし。
良く利用させてもらってるDr.GUI先生のとこで唯一情報があった。
ぶっちゃけこのコラムがなかったらCOM投げてたかもしれん…。
そこには

「Ex」系では各種 VARIANT データ型が作成でき、その配列に関連付けるインターフェイス ID を
指定できます(レコードまたはインターフェイス ポインタ含んだ便利なセーフ配列)。

…とあるが、いまいちピンとこない…。
国内でググって1件、ウェブ全体でググって245件って…

色々調べてみるとユーザー定義の型を登録できるようだ。
その場合、配列の型にはVT_RECORDを指定。
ユーザー定義の型情報はインターフェースとしてアクセスするみたいなので、
もしかしたらGUIDを生成してCOMオブジェクトとして登録…的な感じの壮大なストーリーなのか?
とりあえず今は必要なさそうなので頭の片隅程度に留めておこう…。ハマりそうだし。

関係ありそうなmsdnのページ
http://msdn.microsoft.com/en-us/library/ms221212(VS.85).aspx
http://forums.microsoft.com/MSDN-JA/ShowPost.aspx?PostID=2999991&SiteID=7
なんとなくソースが記述してある海外の質問コーナー
http://archives.neohapsis.com/archives/microsoft/various/dcom/2004-q1/0011.html



参照時のあれこれ

C/C++の配列と違い、生成時に添え字の最小値などを設定するということは、
参照時もその情報を取得した上で参照しなければならないということになる。
全て自分で管理してて情報まる分かり状態であれば、決めうちでlLboundの値を固定にしてしまえばいいが、
例えばCOMオブジェクトから配列を取得した場合、COMオブジェクト側の配列設定に従わなければ参照は出来ない。
例えばExcelワークシートからレンジオブジェクトを取得するとlLbound=1となっているので
添え字は1から参照しなければならない。
つまり軽い気持ちでC/C++でいうa[0]参照をすると、
「あれ…?、値が…、ずれて…、参照されて…、いるよ?」みたいになっちゃうので注意

各次元の添え字範囲取得には SafeArrayGetUBound()SafeArrayGetLBound()を使用する。



メモリ配置

SAFEARRAYのメモリ配置とC/C++での配列のメモリ配置は異なるので注意。
(異なるというか、感覚が狂うような気がする)

例えばC/C++で
int a[2][4];
と宣言すると、メモリ配置的には
a[0]
[0], [1], [2], [3]
a[1]
[0], [1], [2], [3]
のように、左側の添え字を親として、右隣の配列を子として持っているようなイメージ。
よって
int *p = a[0];
としてpをインクリメントすると、[0][0], [0][1]…、というようにアクセスできる。

SAFEARRAYの場合、SAFEARRAYBOUNDを
SAFEARRAYBOUND sab[2];
sab[0].lLbound = 0;
sab[0].cElements = 2;
sab[1].lLbound = 0;
sab[1].cElements = 4;
として配列を生成すると、デバッガで見ると
[0,0]
[1,0]
[0,1]
[1,1]
[0,2]
[1,2]
[0,3]
[1,3]
と並ぶ。つまり右の添え字を親としてデータが並んでいると考えられる。
なので、
long indices[2] = {0, 0};
int *p;
SafeArrayPtrOfIndex( parray, indices, reinterpret_cast<int **>(&p) )
としたとき、
p[0] >> [0,0]
p[1] >> [1,0]
となるので注意が必要。
最終更新:2009年01月27日 14:59
ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。