1

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
Pᴇʜ
  • 53,845
  • 9
  • 46
  • 68
Eric Hu
  • 33
  • 5
  • `RtlMoveMemory` [has](https://docs.microsoft.com/en-us/windows/win32/devnotes/rtlmovememory) three parameters and does not return anything. If you want an array pointer to pass to SafeArray functions, see https://stackoverflow.com/a/183668/11683. – GSerg Apr 09 '21 at 06:51
  • @GSerg, Thanks, I may spend some time to study it. – Eric Hu Apr 09 '21 at 07:06
  • Is this code based on [this](https://www.vbforums.com/showthread.php?736285-VB6-Returning-Detecting-Empty-Arrays&p=4538659&viewfull=1#post4538659) post? What should `fnSafeArrayGetDim` return for `ReDim TestArray(5, 6)` - 2, 5 or 6? – wqw Apr 09 '21 at 20:19
  • @wqw, it should return 2 – Eric Hu Apr 11 '21 at 06:46
  • @EricHu Did you see the linked post? There is already a `PeekSafeArray(PeekArray(aTemp).Ptr).cDims` which does that with no call to `SafeArrayGetDim`. – wqw Apr 11 '21 at 08:19
  • @wqw sorry, I didn't get what you mean, which link? and it's on VB6? – Eric Hu Apr 11 '21 at 23:38
  • @EricHu The link is in my comment above. Search for "Is this code based on this post" if you can't immediately find it. – wqw Apr 12 '21 at 15:07
  • @wqw, thanks for your advising, I just replace "SafeArrayGetDim" by PeekSafeArray, and try to pack PeekSafeArray(PeekArray(varTmpArray).Ptr).cDims as universal function, but it's still "type mismatch" error! – Eric Hu Apr 12 '21 at 15:43
  • It's complicated. I just posted a complete working answer. – wqw Apr 13 '21 at 10:29

2 Answers2

0

Change it to

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

You cannot put a Dim TestArray() As Long in a Dim varTmpArray() As Variant what you try here varTmpArray = varRunArray.

If you want to be more generic then use

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

For example:

You cannot put a Long array into a Variant array

Sub ThisDoesNotWork()
    Dim TestArray() As Long
    ReDim TestArray(3, 2)
    
    Dim varTmpArray() As Variant 'with parenthesis
    varTmpArray = TestArray
End Sub

but you can put a Long array into a Variant (that is not an array)

Sub ThisWorks()
    Dim TestArray() As Long
    ReDim TestArray(3, 2)
    
    Dim varTmpArray As Variant 'note this is without parenthesis!
    varTmpArray = TestArray
End Sub

and you can put a Long array into another Long array

Sub ThisWorksToo()
    Dim TestArray() As Long
    ReDim TestArray(3, 2)
    
    Dim varTmpArray() As Long 'with parenthesis it has to be the same type as TestArray
    varTmpArray = TestArray
End Sub
Pᴇʜ
  • 53,845
  • 9
  • 46
  • 68
  • Peh, yes, I knew the issue, but what if I would like to make the "fnSafeArrayGetDim" function as universal function, and I can't sure which kind of array will be pass to "fnSafeArrayGetDim" function – Eric Hu Apr 09 '21 at 06:56
  • @EricHu Well, see my examples there is a difference between declaring `Dim varTmpArray As Variant` and `Dim varTmpArray() As Variant` you want to go with the first one without the parenthesis then. • But also have a look at GSerg's comment as your function declarations seem to be wrong too. – Pᴇʜ Apr 09 '21 at 06:58
  • Peh, the "ThisWorks()" could work, but once pass the "varTmpArray" as parameter to PeekArray API, the "Type Mismatch" error may encounter again. – Eric Hu Apr 09 '21 at 07:05
  • @EricHu have a look at what GSerg posted or [here](https://stackoverflow.com/a/35275722) your function declaration is wrong! – Pᴇʜ Apr 09 '21 at 07:10
  • @Pᴇʜ His API declaration for `RtlMoveMemory` is not wrong although a bit unusual. It works because in stdcall "large" UDTs as retval are passed on the stack **before** the first actual argument, so in his case the stack layout for the retval coincide with the original declare's `Destination` parameter and only two more actual arguments need to be pushed/declared in addition. Weird but it works, I checked the disassembly when stumbled upon this several years ago. – wqw Apr 09 '21 at 20:29
0

Here is a working fnSafeArrayGetDim function

Option Explicit

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length 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
    Const VT_BYREF      As Long = &H4000
    Dim lVarType        As Long
    Dim lPtr            As Long
    
    Call CopyMemory(lVarType, varRunArray, 2)
    If (lVarType And vbArray) <> 0 Then
        Call CopyMemory(lPtr, ByVal VarPtr(varRunArray) + 8, 4)
        If (lVarType And VT_BYREF) <> 0 Then
            Call CopyMemory(lPtr, ByVal lPtr, 4)
        End If
        If lPtr <> 0 Then
            Call CopyMemory(fnSafeArrayGetDim, ByVal lPtr, 2)
        End If
    End If
End Function

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.

wqw
  • 11,216
  • 1
  • 31
  • 40