空腹おやじのログと備忘録

VBA(主にExcel)でいろいろな実験的な事とか、Linuxのコマンドとか設定とかについて忘れないように、あれこれと・・・

ExcelのVBAで使えるDLLを、C++(Visual Studio 2017)で作る。・・・その4.1(配列 準備編)

はじめに

前回の文字列の受け渡しでは、VARIANTを使いました。(自分でもびっくり)

今回の配列でも、int* とか short* とかを使うのかと思いきや
SAFEARRAYなるものを使うことになります。

VBA の配列は OLE の SafeArrays です。C/C++ については、Windows OLE/COM ヘッダー ファイル内で SAFEARRAY として定義されています。

https://docs.microsoft.com/ja-jp/office/client-developer/excel/how-to-access-dlls-in-excel

ということで、配列の1回目は、SAFEARRAYとはどのようなものなのか、調べてみます。

SAFEARRAY

docs.microsoft.com

SAFEARRAY構造体

こんな感じで定義されています。

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

メンバ内容
cDims配列の次元数
fFeaturesフラグ
cbElements配列の1要素のサイズ
cLocks対応するロック解除なしでアレイがロックされた回数。
原文:The number of times the array has been locked without a corresponding unlock.
よくわかりません・・・ m(_ _)m
pvData配列要素の先頭データを指すポインタ
rgsabound配列の指定次元の
1.要素数
2.指定できるインデックスの下限

cDims
配列の次元数が格納されている。
1次元配列なら、1
2次元配列なら、2
3次元配列なら、3
と言った具合です。

fFeatures
項目備考
FADF_AUTO0x0001配列はスタック上に割り当てられる。
FADF_STATIC0x0002配列は静的に割り当てられる。
FADF_EMBEDDED0x0004配列は構造体に組み込まれる。
FADF_FIXEDSIZE0x0010配列はリサイズ、再割り当てできない。
FADF_RECORD0x0020レコードを含む配列。
FADF_HAVEIID0x0040インターフェイスを識別するIIDを持つ配列。FADF_DISPATCHまたはFADF_UNKNOWNも設定されている場合にのみ設定される。
FADF_HAVEVARTYPE0x0080VARIANT型を持つ配列。
FADF_BSTR0x0100BSTR型の配列。
FADF_UNKNOWN0x0200IUnknown*型の配列。
FADF_DISPATCH0x0400IDispatch*型の配列。
FADF_VARIANT0x0800VARIANT型の配列。
FADF_RESERVED0xF008将来のために予約済み。
VBA側から渡す配列によって異なり、以下のようになるようです。
f:id:Z1000S:20191113210606j:plain
DateTime型、Currency型はいずれも数値型と同じになりました。

cbElements
配列の要素1個のサイズ。
VBAのIntegerの配列なら2、Longの配列なら4
MSのサイトには、「データオブジェクトのサイズは含まれない」と書かれている(が理解できていない・・・)。

pvData
データを指すポインタ。

1次元配列の場合は、普通にインクリメントしても(範囲内であれば)大丈夫。
2次元以上の配列の場合、インクリメントしても、おそらくほとんどの人が意図した要素のアドレスを指していないと思う。

なぜなら、メモリ上の要素の並びが以下のようになっているから。

元データ(Byte型の3次元配列)

x(0, 0, 0) = &H0
x(0, 0, 1) = &H1
x(0, 1, 0) = &H10
x(0, 1, 1) = &H11
x(1, 0, 0) = &H20
x(1, 0, 1) = &H21
x(1, 1, 0) = &H30
x(1, 1, 1) = &H31

SAFEARRAYのpvDataの状態(メモリデータ)
f:id:Z1000S:20191114215409j:plain
次項のrgsaboundでも書いているが、インデックスの並びが左右逆になっている。

混乱する元になるので、多次元配列の場合は、ポインタを使わないようにした方がよさそう・・・

rgsabound
各次元の境界情報
下記構造体で定義されている。

typedef struct tagSAFEARRAYBOUND {
  ULONG cElements;
  LONG  lLbound;
} SAFEARRAYBOUND, *LPSAFEARRAYBOUND;

単一要素の配列となっているのは、
多次元配列の場合に、インデックスに指定する値を変えることで各次元の情報が得られるような仕組みとするためと思われる。
指定できるインデックスは、0 ~ cDims -1
ただし、インデックスの指定には注意が必要。
3次元配列を例にすると、下表のようになる。
配列の1番右の次元がのインデックスがとなる。

rgsabound
インデックス
210
 
配列(1次元目,2次元目,3次元目)

VBA側で、下記配列を用意した場合、下表のような値となる。

Dim lArray(1 To 2, 2 To 4, 3) As Long

項目rgsabound
インデックス
012
cElements432
lLbound021


docs.microsoft.com

次回予告

SAFEARRAYの基礎知識を得たところで、
次回は、実際に配列の受け渡しを行ってみます。
さらに、第3回は、VARIANTの非配列変数に、配列を格納して受け渡しをしてみる予定です。