15

On Windows the declared function RtlMoveMemory provides a way to copy a block of bytes from one address to another:

Private Declare PtrSafe Sub RtlMoveMemory Lib "kernel32" ( _
                              ByVal dest As LongPtr, _
                              ByVal src As LongPtr, _
                              ByVal size As LongPtr)

What is the equivalent on Mac OS X ?

robinCTS
  • 5,746
  • 14
  • 30
  • 37
michael
  • 929
  • 6
  • 19
  • https://www.experts-exchange.com/questions/26694931/excel-vba-how-change-this-class-to-work-for-Mac-excel-2011.html has an accepted answer. Don't know if you're aware of that? – Luuklag Aug 21 '17 at 12:39
  • @Luuklag As far as I saw, they wrote a custom `alloc`. That will not help here, as `RtlMoveMemory`access all process memory. – LS_ᴅᴇᴠ Aug 22 '17 at 09:15
  • Have you seen https://stackoverflow.com/questions/22470863/calling-dylib-functions-in-office-for-mac-vba and https://blackcatsoftware.us/dynamic-libraries-dylib-for-office-using-xcode-5/ ? You might be able to follow similar convention to call memmove from the C lib – kvr Aug 26 '17 at 19:03

2 Answers2

7

What is the equivalent on Mac OS X ?

Short answer:

Private Declare PtrSafe Function CopyMemory Lib "libc.dylib" Alias "memmove" _
                                 ( _
                                    ByVal dest As LongPtr _
                                  , ByVal src As LongPtr _
                                  , ByVal size As LongLong _
                                  ) _
                        As LongPtr

Long answer: Depends ;)


The following is a fully fledged example which uses conditional compilation* to allow it to work on any Mac/Windows/32-bit/64-bit computer. It also demonstrates the two different ways to declare and call the function (by Pointer and by Variable).

#If Mac Then
  #If Win64 Then
    Private Declare PtrSafe Function CopyMemory_byPtr Lib "libc.dylib" Alias "memmove" (ByVal dest As LongPtr, ByVal src As LongPtr, ByVal size As Long) As LongPtr
    Private Declare PtrSafe Function CopyMemory_byVar Lib "libc.dylib" Alias "memmove" (ByRef dest As Any, ByRef src As Any, ByVal size As Long) As LongPtr
  #Else
    Private Declare Function CopyMemory_byPtr Lib "libc.dylib" Alias "memmove" (ByVal dest As Long, ByVal src As Long, ByVal size As Long) As Long
    Private Declare Function CopyMemory_byVar Lib "libc.dylib" Alias "memmove" (ByRef dest As Any, ByRef src As Any, ByVal size As Long) As Long
  #End If
#ElseIf VBA7 Then
  #If Win64 Then
    Private Declare PtrSafe Sub CopyMemory_byPtr Lib "kernel32" Alias "RtlMoveMemory" (ByVal dest As LongPtr, ByVal src As LongPtr, ByVal size As LongLong)
    Private Declare PtrSafe Sub CopyMemory_byVar Lib "kernel32" Alias "RtlMoveMemory" (ByRef dest As Any, ByRef src As Any, ByVal size As LongLong)
  #Else
    Private Declare PtrSafe Sub CopyMemory_byPtr Lib "kernel32" Alias "RtlMoveMemory" (ByVal dest As LongPtr, ByVal src As LongPtr, ByVal size As Long)
    Private Declare PtrSafe Sub CopyMemory_byVar Lib "kernel32" Alias "RtlMoveMemory" (ByRef dest As Any, ByRef src As Any, ByVal size As Long)
  #End If
#Else
  Private Declare Sub CopyMemory_byPtr Lib "kernel32" Alias "RtlMoveMemory" (ByVal dest As Long, ByVal src As Long, ByVal size As Long)
  Private Declare Sub CopyMemory_byVar Lib "kernel32" Alias "RtlMoveMemory" (ByRef dest As Any, ByRef src As Any, ByVal size As Long)
#End If


Public Sub CopyMemoryTest()

  Dim abytDest(0 To 11) As Byte
  Dim abytSrc(0 To 11) As Byte
  Dim ¡ As Long

  For ¡ = LBound(abytSrc) To UBound(abytSrc)
    abytSrc(¡) = AscB("A") + ¡
  Next ¡

  MsgBox "Dest before copy = #" & ToString(abytDest) & "#"
  CopyMemory_byVar abytDest(0), abytSrc(0), 4
  MsgBox "Dest during copy = #" & ToString(abytDest) & "#"
  CopyMemory_byPtr VarPtr(abytDest(0)) + 4, VarPtr(abytSrc(0)) + 4, 4
  MsgBox "Dest during copy = #" & ToString(abytDest) & "#"
  CopyMemory_byPtr VarPtr(abytDest(8)), VarPtr(abytSrc(8)), 4
  MsgBox "Dest after copy = #" & ToString(abytDest) & "#"

End Sub

Public Function ToString(ByRef pabytBuffer() As Byte) As String
  Dim ¡ As Long
  For ¡ = LBound(pabytBuffer) To UBound(pabytBuffer)
    ToString = ToString & Chr$(pabytBuffer(¡))
  Next ¡
End Function

Explanation:

The Mac version of the CopyMemory function returns a result whilst the Win version does not. (The result is the dest pointer, unless an error occurred. See the memmove reference here.) Both versions, however, can be used in exactly the same way, without brackets.

The declare differences are as follows:

  • 64-bit Mac/Win VBA7:

    • Use the PtrSafe keyword
    • Use the Any type for all ByRef parameters
    • Use the LongPtr type for ByVal handle/pointer parameters/return values
    • Use the LongLong type appropriately for other return values/parameter
  • 32-bit Win VBA7:

    • Use the PtrSafe keyword
    • Use the Any type for all ByRef parameters
    • Use the LongPtr type for ByVal handle/pointer parameters/return values
    • Use the Long (not LongLong) type appropriately for other return values/parameter
  • 32-bit Mac/Win VBA6:

    • No PtrSafe keyword
    • Use the Any type for all ByRef parameters
    • Use the Long type for ByVal handle/pointer parameters/return values
    • Use the Long type appropriately for other return values/parameter

Caveats:

  • Tested on Mac Excel 2016 64-bit.
  • Tested on Windows Excel 2007 32-bit.

    • Seems Excel 2007 has issues related to double word alignment. In this example:

      CopyMemory_byVar abytDest(0), abytSrc(0), 4 '-> ABCD CopyMemory_byVar abytDest(1), abytSrc(1), 8 '-> ABCDEFGHI

      CopyMemory() skips all copying until a double word alignment is reached (3 skips in this case), then continues copying from the 4th byte.


Note: If you are curious about my variable naming convention, it is based on RVBA.

*The correct way.

robinCTS
  • 5,746
  • 14
  • 30
  • 37
  • Very clear, but these are slightly excessive I think: You check `#If VBA7` - this ensures `LongPtr` is defined. There is no need then to check `#If Win64`. `Private Declare PtrSafe Sub CopyMemory_byPtr Lib "kernel32" Alias "RtlMoveMemory" (ByVal dest As LongPtr, ByVal src As LongPtr, ByVal size As LongPtr)` will work on both 32 and 64 bit systems. Also, in your Mac declarations, you use `Long` on 64 bit - is this correct? For windows, `size/length` in `RtlMoveMemory` is of type `SIZE_T` which is an alias to `ULONG_PTR` - i.e. VBA should use `LongPtr` - is the same true for Macs' `memmove`? – Greedo Apr 04 '20 at 11:28
2

I looked up RtlMoveMemory() on msdn.microsoft.com and it appears to simply copy a number of bytes from one address to another, handing the special case of overlapping memory.

The Darwin equivalent is void* memmove(void *dst, const void *src, size_t len)

The primitive/faster version is void* memcpy(void *restrict dst, const void *restrict src, size_t n), which does not handle overlapping memory regions.

Do a man memmove for the details.

James Bucanek
  • 3,299
  • 3
  • 14
  • 30
  • How do you access `memmove`/`memcpy` from VBA, though? I'm assuming the OP is writing a VBA macro for use on Windows as well as Mac OS X, or porting a script from Windows to Mac OS X. – IInspectable Aug 25 '17 at 20:08
  • Yes, `RtlMoveMemory` is a public wrapper around `memmove`. The equivalent of `memcpy` would be `RtlCopyMemory`. The question, though, is why you'd need to do this. People use `RtlMoveMemory` in VBA to interact with the Windows API. If you're interacting with the Cocoa APIs, you should probably call Cocoa memory management functions instead of low-level standard library functions. – Cody Gray - on strike Aug 26 '17 at 15:19