VBA での 配列 の作成は以下のような手段があります。
Dim fixedSizeArray(2) As String fixedSizeArray(0) = "あいうえお" fixedSizeArray(1) = "かきくけこ" fixedSizeArray(2) = "さしすせそ"
Dim dynamicArray() As String ReDim dynamicArray(2) dynamicArray(0) = "あいうえお" dynamicArray(1) = "かきくけこ" dynamicArray(2) = "さしすせそ"
Dim variantArray As Variant variantArray = VBA.Array("あいうえお", "かきくけこ", "さしすせそ")
※ Option Base に左右されずにインデックスを 0 から始めたい場合,VBA. と前に付けます。
※ 変数自体は Variant型 ですが,配列のように
variantArray(0)
のように記述可能です。VBA側にカラクリが用意されているからでしょう。
※ 変数自体が Variant型 だけでなく,要素の型も Variant型 になります。
※ 以下のようにした場合は, 2. の 動的配列の要素が Variant型 である配列になります。
Dim dynamicVariantArray() As Variant ' 要素が Variant型 の配列のみ可。
dynamicVariantArray = VBA.Array("あいうえお", "かきくけこ", "さしすせそ")
VBA の 配列 は,オートメーション仕様にそった
SAFEARRAY
という構造体を利用しています。
配列というと,要素が淡々と並んでいるイメージですが,
配列の変数が,その淡々と並んでいる要素たちにたどり着く前に
上記の SAFEARRAY構造体 を経由する仕組みになっています。
つまり,上記の
1. の fixedsizeArray
と
2. の dynamicArray
の変数は,変数自体が SAFEARRAY構造体 というわけではないということです。
では,配列の変数自体はどういうものか?というと,
別の場所に存在している SAFEARRAY構造体 の居場所(アドレス値)を記憶
しています。
(※ 但し,ユーザー定義型(UDT)のメンバとしての静的配列の変数はデータそのものが存在します。 ただ,このページの最後に出てくる IsStaticFixedsizeArray関数 でなぜかチェックできます。)
実際のデータが,変数そのものの場所ではなく,[他の場所] にある時,
その [他の場所] を指し示す変数を ポインタ変数 と呼びます。
ポインタ変数に入れるものは,今回の場合だと,
SAFEARRAY構造体へのポインタ (a pointer to a SAFEARRAY struct)
と表現します。実際は,値 なので ポインタ値 といいます。
また,VBA のようなポインタを直接扱わない言語の場合は
SAFEARRAY構造体への参照 (a reference to a SAFEARRAY struct)
と表現したりします。
(※ SAFEARRAY構造体の別名(alias) という表現もあります。)
fixedsizeArray や dynamicArray の変数自体は 4バイト になります。
上記の 3. の
variantArray
は,要素が Variant型 の配列ですが,
変数自体も Variant型 と呼ばれている構造体になります。
構造体の中に SAFEARRAY構造体へのポインタ が入っているという形になります。
Variant型は,C++で表現すると以下のような構造体になっています。
typedef /* [wire_marshal] */ struct tagVARIANT VARIANT; struct tagVARIANT { union { struct __tagVARIANT { VARTYPE vt; WORD wReserved1; WORD wReserved2; WORD wReserved3; union { LONGLONG llVal; LONG lVal; BYTE bVal; SHORT iVal; FLOAT fltVal; DOUBLE dblVal; VARIANT_BOOL boolVal; _VARIANT_BOOL bool; SCODE scode; CY cyVal; DATE date; BSTR bstrVal; IUnknown *punkVal; IDispatch *pdispVal; SAFEARRAY *parray; BYTE *pbVal; SHORT *piVal; LONG *plVal; LONGLONG *pllVal; FLOAT *pfltVal; DOUBLE *pdblVal; VARIANT_BOOL *pboolVal; _VARIANT_BOOL *pbool; SCODE *pscode; CY *pcyVal; DATE *pdate; BSTR *pbstrVal; IUnknown **ppunkVal; IDispatch **ppdispVal; SAFEARRAY **pparray; VARIANT *pvarVal; PVOID byref; CHAR cVal; USHORT uiVal; ULONG ulVal; ULONGLONG ullVal; INT intVal; UINT uintVal; DECIMAL *pdecVal; CHAR *pcVal; USHORT *puiVal; ULONG *pulVal; ULONGLONG *pullVal; INT *pintVal; UINT *puintVal; struct __tagBRECORD { PVOID pvRecord; IRecordInfo *pRecInfo; } __VARIANT_NAME_4; } __VARIANT_NAME_3; } __VARIANT_NAME_2; DECIMAL decVal; } __VARIANT_NAME_1; } ;
2か所ある union と書いてあるところは { と } の間にある型のどれかになるという意味です。
ごちゃごちゃしていますが,大きさは 16バイト あります。
VBA では Variant型 と小文字混じりですが,C++ では VARIANT型 と全部大文字になります。
VARIANT型 内のメンバで今回関係あるのは,
まず,内側の union の中にある
SAFEARRAY *parray;
と
SAFEARRAY **pparray;
の2箇所になります。
VBA で書くと
Public Type Variant_PArray
vt As Integer
wReserved1 As Integer
wReserved2 As Integer
wReserved3 As Integer
parray As Long
notused As Long
End Type
と
Public Type Variant_PPArray
vt As Integer
wReserved1 As Integer
wReserved2 As Integer
wReserved3 As Integer
pparray As Long
notused As Long
End Type
のような感じになります。
重要なのは (Integer は2バイトで,それが前に4つあるので)
parray や pparray の位置が先頭(0バイト目)から数えて 8 バイト目にある
ということです。
先に書いたように 3. の variantArray 自体は Variant型の構造体です。
その構造体の先頭から 8バイト目からの4バイトに
SAFEARRAY構造体 が存在する場所(アドレス値) が入っています。
parray と pparray の違いは,
parray には SAFEARRAY構造体の場所(アドレス値) が入っていますが,
pparray には SAFEARRAY構造体の場所(アドレス値)を入れておく場所自体の場所(アドレス値)
が入っているというところです。
以下のような
parray の値が指すもの ---> SAFEARRAY構造体の先頭
pparray の値が指すもの ---> SAFEARRAY構造体のアドレスが入っている場所 ---> SAFEARRAY構造体の先頭
感じになります。
今回の説明では,
Variant型 の pparray には parray 自体の場所を示す値が入っている
というところが重要になります。
・ 参照渡し
VBA でプロシージャ(関数) の引数に 変数 を 参照渡し する時は,
実際は,変数の場所が渡されるような感じになります。
例えば,
Function Test(ByRef refArr As Variant) As Boolean ' ByRef はデフォルトなので省略可能
のようなプロシージャに上記の fixedSizeArray変数 や dynamicArray変数 が渡されると
Variant型 の 変数 である refArr には pparray として
[SAFEARRAY構造体の場所を表すアドレス値を入れる場所] を表す アドレス値
がセットされます。
このカラクリのおかげで,配列の変数のアドレス値を得ることができるようになります。
また,理由はわかりませんが,構造体の中の静的配列のSAFEARRAY構造体の情報でも取得できます。
VARIANT 構造体のメンバでもう一つ説明しておかないといけないのが
VARTYPE vt;
という箇所になります。 この vt には union 内のどのメンバが有効か? という情報が入っています。 今回重要なのは
VT_ARRAY (0x2000) ※ VBAだと &H2000
という値と
VT_BYREF (0x4000) ※ VBAだと &H4000
という値になります。フラグのような形で他の値と組み合わさって vt にセットされます。
上記の2つのうち
VT_ARRAY のみがセットされているときは parray が有効
VT_BYREF と VT_ARRAY の両方がセットされているときは pparray が有効
になります。
SAFEARRAY構造体は,C++で表現すると以下のような構造になっています。
typedef struct tagSAFEARRAY { USHORT cDims; USHORT fFeatures; ULONG cbElements; ULONG cLocks; PVOID pvData; SAFEARRAYBOUND rgsabound[ 1 ]; } SAFEARRAY;
今回重要になってくるのが,2番目の fFeatures メンバになります。 先頭(0バイト目)から2バイト目の位置にあります。 この fFeatures には以下の値が設定されます。 VBA で表現すると
Const FADF_AUTO = &H1 ' An array that is allocated on the stack. ' 静的配列は SAFEARRAY構造体はスタックに配置されるが ' この値は設定されない。 ' SAFEARRAY構造体の pvData が指す実際の配列データはヒープ上に作成される。 Const FADF_STATIC = &H2 ' An array that is statically allocated. ' いわゆる静的に作成される配列にこの値が設定される。 Const FADF_EMBEDDED = &H4 ' An array that is embedded in a structure. ' 構造体のメンバの時に設定される。 Const FADF_FIXEDSIZE = &H10 ' An array that may not be resized or reallocated. ' VBA の 静的配列 に設定される。 ' 以下の3つの情報は各要素の型に沿った操作を行う際に利用されるもの。 Const FADF_RECORD = &H20 ' An array that contains records. ' When set, there will be a pointer to the IRecordinfo interface ' at negative offset 4 in the array descriptor. ' ここに出てくる record とは,ユーザー定義の構造体(UDT) のこと。 Const FADF_HAVEIID = &H40 ' An array that has an IID identifying interface. ' When set, there will be a GUID at negative offset 16 ' in the safe array descriptor. ' Flag is set only when FADF_DISPATCH or FADF_UNKNOWN is also set. Const FADF_HAVEVARTYPE = &H80 ' An array that has a VT type. ' When set, there will be a VT tag at negative offset 4 ' in the array descriptor that specifies the element type. ' 以下の4つの情報も各要素の型に沿った操作を行う際に利用されるもの。 Const FADF_BSTR = &H100 ' An array of BSTRs. Const FADF_UNKNOWN = &H200 ' An array of IUnknown*. Const FADF_DISPATCH = &H400 ' An array of IDispatch*. Const FADF_VARIANT = &H800 ' An array of VARIANTs. 'Const FADF_RESERVED = &HF008
のようになります。
今回重要になるのは,
FADF_STATIC --- 静的に割り当てられる配列
と
FADF_FIXEDSIZE --- サイズ変更や再割り当て禁止の配列
になります。
VBA でいうところの 静的配列 の時は,FADF_STATIC と FADF_FIXEDSIZE の両フラグが立つようです。
今回は直接は関係ありませんが,
上記のSAFEARRAY構造体のメンバである pvData に
実際のいわゆる たんたんと並んでいる配列 の先頭の場所(アドレス値)が入っています。
以下が,静的配列を確かめるコードになります。
上で述べたように参照渡しの関係で,仮引数 refArr には VT_BYREF が追加されます。
' ByRef 渡しの時 ( ByVal を付けない時) は, ' 変数のアドレス値が Destination や Source に渡る。 ' ' アドレス値を渡す時は ByVal を付けて渡すこと。 ' ' 変数のアドレス値からのオフセット値を渡す時は, ' ' ByVal VarPtr(変数) + オフセット値 ' ' のように渡す。 Private Declare Sub MoveMemory Lib "Kernel32" Alias "RtlMoveMemory" _ (Destination As Any, Source As Any, ByVal Length As Long) ' 静的配列の時は True を返す。そもそも配列では無い時や動的配列の時は False を返す。 Public Function IsStaticFixedsizeArray(ByRef refArr As Variant) As Boolean On Error GoTo Err_h IsStaticFixedsizeArray = False Dim parray As Long Dim vt As Integer ' VARENUM の関係分 Const VT_BYREF As Integer = &H4000 Const VT_ARRAY As Integer = &H2000 ' vbArray でも可 8192 ' VARIANT構造体 の vt の値を取得 Call MoveMemory(vt, refArr, 2) If (vt And VT_ARRAY) = 0 Then ' 配列ではない 'Debug.Print "VT_ARRAY ではない。" Exit Function End If ' VARIANT構造体 の 8バイト目に SAFEARRAY *parray がある。 Call MoveMemory(parray, ByVal VarPtr(refArr) + 8, 4) ' ※ 4 は 32ビットVBAの時 ' さらに VT_BYREF がセットされているか? If (vt And VT_BYREF) <> 0 Then ' Variant型に入っていない配列は ' 引数に渡される際に SAFEARRAY への "ポインタのアドレス" ' つまり ポインタのポインタ が ' SAFEARRAY **pparray として入っているため ' 8バイト目は parray ではなく pparray なので, ' それを逆参照して parray を得る。 Call MoveMemory(parray, ByVal parray, 4) ' parray の中身はアドレス値 ※ 4 は 32ビットVBAの時 End If If parray = 0 Then ' parray が NULL なのは, ' 一度も ReDim していないか Erase された動的配列だろう。 Exit Function End If Dim fFeatures As Integer ' USHORT ' SAFEARRAY構造体の fFeatures の値を取得。 Call MoveMemory(fFeatures, ByVal parray + 2, 2) ' fFeatures にセットされるフラグで静的配列に関係あるもの。 Const FADF_STATIC As Integer = &H2 ' An array that is statically allocated. Const FADF_FIXEDSIZE As Integer = &H10 ' An array that may not be resized or reallocated. ' 静的配列 は FADF_STATIC と FADF_FIXEDSIZE のフラグが立つ。 Const FADF_STATIC_FIXEDSIZE As Integer = FADF_STATIC Or FADF_FIXEDSIZE If (fFeatures And FADF_STATIC_FIXEDSIZE) = FADF_STATIC_FIXEDSIZE Then IsStaticFixedsizeArray = True End If Ext_h: Exit Function Err_h: MsgBox Err.Number & vbCrLf & Err.Description, vbOKOnly Or vbCritical, "IsStaticFixedsizeArray 内でエラー" Resume Ext_h End Function
動的配列が Erase 済みかどうか?を調べるには,上記の
If parray = 0 Then
の箇所で判定すればわかります。
' ByRef 渡しの時 ( ByVal を付けない時) は, ' 変数のアドレス値が Destination や Source に渡る。 ' ' アドレス値を渡す時は ByVal を付けて渡すこと。 ' ' 変数のアドレス値からのオフセット値を渡す時は, ' ' ByVal VarPtr(変数) + オフセット値 ' ' のように渡す。 Private Declare Sub MoveMemory Lib "Kernel32" Alias "RtlMoveMemory" _ (Destination As Any, Source As Any, ByVal Length As Long) ' 動的配列が Erase済み or ReDim していない 時は True を返す。そもそも配列では無い時や静的配列の時は False を返す。 Public Function IsErasedDynamicArray(ByRef refArr As Variant) As Boolean On Error GoTo Err_h IsErasedDynamicArray= False Dim parray As Long Dim vt As Integer ' VARENUM の関係分 Const VT_BYREF As Integer = &H4000 Const VT_ARRAY As Integer = &H2000 ' vbArray でも可 8192 ' VARIANT構造体 の vt の値を取得 Call MoveMemory(vt, refArr, 2) If (vt And VT_ARRAY) = 0 Then ' 配列ではない 'Debug.Print "VT_ARRAY ではない。" Exit Function End If ' VARIANT構造体 の 8バイト目に SAFEARRAY *parray がある。 Call MoveMemory(parray, ByVal VarPtr(refArr) + 8, 4) ' ※ 4 は 32ビットVBAの時 ' さらに VT_BYREF がセットされているか? If (vt And VT_BYREF) <> 0 Then ' Variant型に入っていない配列は ' 引数に渡される際に SAFEARRAY への "ポインタのアドレス" ' つまり ポインタのポインタ が ' SAFEARRAY **pparray として入っているため ' 8バイト目は parray ではなく pparray なので, ' それを逆参照して parray を得る。 Call MoveMemory(parray, ByVal parray, 4) ' parray の中身はアドレス値 ※ 4 は 32ビットVBAの時 End If If parray = 0 Then ' parray が NULL なのは, ' 一度も ReDim していないか Erase された動的配列だろう。 IsErasedDynamicArray= True Exit Function End If Ext_h: Exit Function Err_h: MsgBox Err.Number & vbCrLf & Err.Description, vbOKOnly Or vbCritical, "IsErasedDynamicArray内でエラー" Resume Ext_h End Function
Published: 2011-07-06
Last Updated: 2013-06-14
つくれますの部屋
Copyright(C) 2011 Yasuharu Takahashi, All Rights Reserved.