Wednesday, 8 March 2017

Get Array Dimensions and Bounds (REDUX)

Previously, I opined on a Stack Overflow Q & A page where the classic interview question about finding the number of dimension for an array How to return the number of dimensions of a Variant array passed to it in VBA  On that SO Q & A you'll find my C# code that returns not just but actually the dimensions but also the bounds of the array's dimensions because that very frequentl follows.

But I want a pure VBA solution to paste into my projects, so here is some code.  We use the CopyMemory technique because this avoids using On Error Resume Next.



Option Explicit
Option Private Module

'http://stackoverflow.com/questions/24613101/vba-check-if-array-is-one-dimensional/26555865#26555865

Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByVal Destination As Long, _
        ByVal Source As Long, ByVal Length As Integer)

Public Function GetDimsAndBounds(VarSafeArray As Variant) As Scripting.Dictionary

    Dim dic As Scripting.Dictionary
    Set dic = New Scripting.Dictionary
    
    Dim lDims As Long
    lDims = GetDims(VarSafeArray)
    
    Dim lDimLoop As Long
    For lDimLoop = 1 To lDims
        
        ReDim bounds(0 To 1)
        bounds(0) = LBound(VarSafeArray, lDimLoop)
        bounds(1) = UBound(VarSafeArray, lDimLoop)
        dic.Add lDimLoop, bounds
    
    Next
    Set GetDimsAndBounds = dic

End Function

Public Function GetDims(VarSafeArray As Variant) As Integer
    Dim variantType As Integer
    Dim pointer As Long
    Dim arrayDims As Integer

    CopyMemory VarPtr(variantType), VarPtr(VarSafeArray), 2& 'the first 2 bytes of the VARIANT structure contain the type

    If (variantType And &H2000) > 0 Then 'Array (&H2000)
        'If the Variant contains an array or ByRef array, a pointer for the SAFEARRAY or array ByRef variant is located at VarPtr(VarSafeArray) + 8
        CopyMemory VarPtr(pointer), VarPtr(VarSafeArray) + 8, 4&

        'If the array is ByRef, there is an additional layer of indirection through another Variant (this is what allows ByRef calls to modify the calling scope).
        'Thus it must be dereferenced to get the SAFEARRAY structure
        If (variantType And &H4000) > 0 Then 'ByRef (&H4000)
            'dereference the pointer to pointer to get the actual pointer to the SAFEARRAY
            CopyMemory VarPtr(pointer), pointer, 4&
        End If
        'The pointer will be 0 if the array hasn't been initialized
        If Not pointer = 0 Then
            'If it HAS been initialized, we can pull the number of dimensions directly from the pointer, since it's the first member in the SAFEARRAY struct
            CopyMemory VarPtr(arrayDims), pointer, 2&
            GetDims = arrayDims
        Else
            GetDims = 0 'Array not initialized
        End If
    Else
        GetDims = 0 'It's not an array... Type mismatch maybe?
    End If
End Function




No comments:

Post a Comment