Search code examples
arraysvb6

How to get Array Dimension(array parameter pass error)?


I'm trying to get the dimension of an array via PeekArray and SafeArrayGetDim API, But the "Type mismatch" when compiling. And if Debug.Print SafeArrayGetDim(PeekArray(TestArray).Ptr) will work fine.

Please find below the VB code. Any help will be greatful.

Option Explicit

Private Type PeekArrayType
    Ptr As Long
    Reserved As Currency
End Type

Private Declare Function PeekArray Lib "kernel32" Alias "RtlMoveMemory" ( _
    Arr() As Any, Optional ByVal Length As Long = 4) As PeekArrayType

Private Declare Function SafeArrayGetDim Lib "oleaut32.dll" (ByVal Ptr As Long) As Long


Sub GetArrayDimension()
    Dim TestArray() As Long
    ReDim TestArray(3, 2)
    Debug.Print fnSafeArrayGetDim(TestArray)
End Sub


Function fnSafeArrayGetDim(varRunArray As Variant) As Long
    Dim varTmpArray() As Variant
    varTmpArray = varRunArray
    fnSafeArrayGetDim = SafeArrayGetDim(PeekArray(varTmpArray).Ptr)
End Function

Solution

  • Here is a working fnSafeArrayGetDim function

    Option Explicit
    
    #Const HasPtrSafe = (VBA7 <> 0) Or (TWINBASIC <> 0)
    
    #If Win64 Then
        Private Const PTR_SIZE                  As Long = 8
    #Else
        Private Const PTR_SIZE                  As Long = 4
    #End If
    
    #If HasPtrSafe Then
        Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
    #Else
        Private Enum LongPtr
            [_]
        End Enum
        Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
    #End If
    
    Public Function fnSafeArrayGetDim(varRunArray As Variant) As Long
        Const VT_BYREF      As Long = &H4000
        Dim lVarType        As Long
        Dim lPtr            As LongPtr
        
        Call CopyMemory(lVarType, varRunArray, 2)
        If (lVarType And vbArray) <> 0 Then
            Call CopyMemory(lPtr, ByVal VarPtr(varRunArray) + 8, PTR_SIZE)
            If (lVarType And VT_BYREF) <> 0 Then
                Call CopyMemory(lPtr, ByVal lPtr, PTR_SIZE)
            End If
            If lPtr <> 0 Then
                Call CopyMemory(fnSafeArrayGetDim, ByVal lPtr, 2)
            End If
        End If
    End Function
    
    Private Sub Form_Load()
        Dim TestArray() As Long
        ReDim TestArray(3, 2)
        Debug.Print fnSafeArrayGetDim(TestArray)
    End Sub
    

    You don't need PeekArray as you are dealing with pure Variants not arrays like Variant() (array of Variants), Long() (array of Longs) or Byte() (array of Bytes) generally a type ending with () in VB6 is so called SAFEARRAY in COM parlance.

    So your varRunArray is a pure Variant that points to a SAFEARRAY in its pparray member which is located at VarPtr(varRunArray) + 8. Once you get this pointer you must heed the VT_BYREF flag in Variant's vt which introduces a double indirection (you have to dereference lPtr = *lPtr once more). At this point if you get a non-NULL pointer to the SAFEARRAY structure then the cDim member is in the first 2 bytes.