VBAの配列が 静的配列 であることを確認する方法について

VBA での 配列 の作成は以下のような手段があります。

1. 静的配列 --- 後で要素数の変更が効かない

Dim fixedSizeArray(2) As String
    
fixedSizeArray(0) = "あいうえお"
fixedSizeArray(1) = "かきくけこ"
fixedSizeArray(2) = "さしすせそ"

2. 動的配列 --- 後で要素数の変更が可能(※最後の次元のみ)

Dim dynamicArray() As String
    
ReDim dynamicArray(2)
    
dynamicArray(0) = "あいうえお"
dynamicArray(1) = "かきくけこ"
dynamicArray(2) = "さしすせそ"

3. VBA の Array関数 を使い Variant型 にセットした配列 (これも動的配列に仲間)

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構造体 について

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型 と全部大文字になります。

 

・ parray と pparray

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つあるので)

parraypparray の位置が先頭(0バイト目)から数えて 8 バイト目にある

ということです。

先に書いたように 3. の variantArray 自体は Variant型の構造体です。
その構造体の先頭から 8バイト目からの4バイトに
SAFEARRAY構造体 が存在する場所(アドレス値) が入っています。

parraypparray の違いは,

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構造体の情報でも取得できます。

 

・ VARTYPE

VARIANT 構造体のメンバでもう一つ説明しておかないといけないのが

VARTYPE vt;

という箇所になります。 この vt には union 内のどのメンバが有効か? という情報が入っています。 今回重要なのは

VT_ARRAY   (0x2000) ※ VBAだと &H2000

という値と

VT_BYREF   (0x4000) ※ VBAだと &H4000

という値になります。フラグのような形で他の値と組み合わさって vt にセットされます。
上記の2つのうち

VT_ARRAY のみがセットされているときは parray が有効

VT_BYREFVT_ARRAY の両方がセットされているときは pparray が有効

になります。

 

SAFEARRAY構造体 のメンバについて

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.