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

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

【VBA】非配列のVariant型変数に格納した配列のLBoundや要素数を変えてみた

SafeArrayRedim を使うと、
非配列のVariant型変数に格納した配列でも、LBoundとかElementsとかの変更ができる。

だまって、
ReDim Preserve v(2 To 4)
とかすればいいんだけど・・・

ソース
Private Type SAFEARRAYBOUND
    cElements   As Long
    lLbound     As Long
End Type

Private Declare Function SafeArrayRedim Lib "OleAut32" (ByVal psa As Long, ByRef psaboundNew As SAFEARRAYBOUND) As Long

Private Declare Sub MoveMemory Lib "Kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)


Public Sub redimVariantArray()

    Dim v   As Variant

    v = Array(1, 2, 3)

    Dim psa As Long
    Dim isDynamicArray  As Boolean

    psa = getSafeArrayPointer(v, isDynamicArray)

    If psa = 0 Then
        Debug.Print "Target is not initialized."

        Exit Sub
    ElseIf Not isDynamicArray Then
        Debug.Print "Target is not Dynamic Array."

        Exit Sub
    End If

    Debug.Print "Before"
    Debug.Print "LBound:" & LBound(v)
    Debug.Print "UBound:" & UBound(v)
    Debug.Print ""

    Dim sab As SAFEARRAYBOUND

    sab.cElements = UBound(v) - LBound(v) + 1
    sab.lLbound = LBound(v) + 2

    Call SafeArrayRedim(Not Not psa, sab)

    Debug.Print "After"
    Debug.Print "LBound:" & LBound(v)
    Debug.Print "UBound:" & UBound(v)

End Sub

Public Sub redimVariantRefArray()

    Dim v   As Variant
    Dim lArray() As Long

    ReDim lArray(3)

    lArray(0) = 2
    lArray(1) = 3
    lArray(2) = 4
    lArray(3) = 5

    v = lArray

    Dim psa As Long
    Dim isDynamicArray  As Boolean

    psa = getSafeArrayPointer(v, isDynamicArray)

    If psa = 0 Then
        Debug.Print "Target is not initialized."

        Exit Sub
    ElseIf Not isDynamicArray Then
        Debug.Print "Target is not Dynamic Array."

        Exit Sub
    End If

    Debug.Print "Before"
    Debug.Print "LBound:" & LBound(v)
    Debug.Print "UBound:" & UBound(v)
    Debug.Print ""

    Dim sab As SAFEARRAYBOUND

    sab.cElements = UBound(v) - LBound(v) + 2
    sab.lLbound = LBound(v) + 2

    Call SafeArrayRedim(Not Not psa, sab)

    Debug.Print "After"
    Debug.Print "LBound:" & LBound(v)
    Debug.Print "UBound:" & UBound(v)

End Sub

Public Function getSafeArrayPointer(ByRef v As Variant, ByRef isDynamicArray As Boolean) As Long

    Const VT_ARRAY          As Integer = &H2000
    Const VT_BYREF          As Integer = &H4000

    Const FADF_STATIC       As Integer = &H2
    Const FADF_FIXEDSIZE    As Integer = &H10

    Const OFFSET_VT         As Long = 0&
    Const OFFSET_PARRAY     As Long = 8&
    Const OFFSET_FFEATURES  As Long = 2&

    Dim pArray  As Long
    Dim vt      As Integer

    isDynamicArray = False

    Call MoveMemory(vt, v, Len(vt))

    If (vt And VT_ARRAY) = 0 Then
        '配列ではない
        Exit Function
    End If

    'VARIANTのparrayのアドレス
    Call MoveMemory(pArray, ByVal VarPtr(v) + OFFSET_PARRAY, Len(pArray))

    If (vt And VT_BYREF) = VT_BYREF Then
        'VARIANTのpparrayから、parrayのアドレスを引っ張ってくる
        Call MoveMemory(pArray, ByVal pArray, Len(pArray))
    End If

    If pArray = 0 Then
        'Redimがまだか、Eraseした後
        Exit Function
    End If

    Dim fFeatures   As Integer

    Call MoveMemory(fFeatures, ByVal pArray + OFFSET_FFEATURES, Len(fFeatures))

    isDynamicArray = CBool((fFeatures And (FADF_STATIC Or FADF_FIXEDSIZE)) <> (FADF_STATIC Or FADF_FIXEDSIZE))

    getSafeArrayPointer = pArray

End Function
実行結果

redimVariantArray

Before
LBound:0
UBound:2

After
LBound:2
UBound:4

f:id:Z1000S:20191206233348g:plain

redimVariantRefArray

Before
LBound:0
UBound:3

After
LBound:2
UBound:6