CopyMemory and Arrays: Proper Use

Datatype Exceptions

Although CopyMemory achieves great speeds in comparison to the good ol' For..Next loop, there are some datatypes you just can't include in the UDT (or the element type for the array) without special handling. These are variable-length strings, and COM object variables.

The String Datatype

The variable string datatype (... As String) occupies 4 bytes. These 4 bytes are filled with a pointer that points to the first character of the string in memory. Visual Basic handles automatically the allocation/deallocation of the memory occupied by the string characters. But, if you override the pointer by using CopyMemory, you will most likely get a Access Violation error when Visual Basic accesses the supposedly not-used pointer. In order to avoid this, you must get rid of any "residual" pointer as well as any memory allocated, if necessary (not necessary in the examples treated here). The following example shows how to override a string safely with CopyMemory. Note that you must do this cleanup for every single string variable you "free" for use with CopyMemory.

Option Explicit

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSrc As Any, ByVal ByteLen As Long)

Public Sub Main()

Dim strString1 As String
Dim strString2 As String

    strString1 = "This string will get moved from strString1 to strString2."
    MsgBox "strString1:" & vbCrLf & vbCrLf & strString1, vbInformation
    CopyMemory strString2, strString1, 4
    'Override with 0 the string pointer stored in strString1.
    'If this is not done, when the variables strString1 and strString2 go out
    'of scope, Visual Basic will attempt deallocation of the memory associated
    'with the strings, assuming that no two variables can point to the same string,
    'which is normally true if you don't hack the variable contents using
    'CopyMemory.
    CopyMemory strString1, 0&, 4
    MsgBox "From strString2:" & vbCrLf & vbCrLf & strString2, vbInformation
End Sub

COM Object Datatypes

The other restriction when using CopyMemory are the object datatypes. Any object variable, like Picture, Font, Object, Form, etc. will present a Access Violation error if you do not clean up the same as you would with strings. This is because of the hidden calls to the AddRef() and Release() methods of the IUnknown interface for the object. I cover a little more in-depth about this problem in my article Circular Referencing to COM Objects. And by the way, the soft referencing method explained in that article can be used in place of the following cleanup method to avoid the Access Violation error.

Option Explicit

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSrc As Any, ByVal ByteLen As Long)

Public Sub Main()

Dim oFont1 As Font
Dim oFont2 As Font

    Set oFont1 = New StdFont
    oFont1.Name = "Tahoma"
    MsgBox "oFont1.Name: " & oFont1.Name, vbInformation
    CopyMemory oFont2, oFont1, 4
    'Clean the first variable, just the same as you would with a string
    CopyMemory oFont1, 0&, 4
    MsgBox "oFont2.Name: " & oFont2.Name, vbInformation
End Sub

Summarizing...

Wrapping up these exceptions should be straightforward for you now, but just to make things clear, I will pose a version of PopulateArrayAPI in the case the UDT had any of the exception datatypes. Note the use of the API function ZeroMemory. This function effectively clean up the variables inside the UDT array element, wiping away any problem.

Private Declare Sub ZeroMemory Lib "kernel32.dll" Alias "RtlZeroMemory" (Destination As Any, ByVal Length As Long)

'This is the modified version of PopulateArrayAPI to take into account the use of strings or object
'datatypes in the UDT TypeID
'
'Example:
'
'Public Type TypeID
'    lID As Long
'    strName As String
'    oDispFont As Font
'    bTask1 As Boolean
'    bTask2 As Boolean
'End Type
Public Sub PopulateArrayAPI(ByRef arrData() As TypeID, ByVal lTotal As Long, ByVal oCB As ICallBack)

Dim lCount As Long
Dim lID As Long
Dim lPos As Long
Dim bFound As Boolean

    Randomize
    For lCount = 1 To lTotal
        'Get an ID number that is not in the array
        Do
            lID = oCB.NewID(lTotal)
            'Find the ID in the array
            lPos = QuickSortFindID(lID, arrData, bFound)
        Loop Until Not (bFound)
        ReDim Preserve arrData(1 To lCount)
        If (lPos = -1) Or (lPos = lCount) Then
            'First element in the array
            'No cleanup necessary here
            arrData(lCount).lID = lID
        Else
            'lPos contains the nearest index whose ID is greater than lID,
            'which is the position where the new ID must be.
            CopyMemory arrData(lPos + 1), arrData(lPos), (lCount - lPos) * LenB(arrData(LBound(arrData)))
            'Now the data has been moved and position lPos is free to use!
            'You MUST clean up the lPos slot before the assigntion.
            'If you don't do it, a Access Violation error will arise
            ZeroMemory arrData(lPos), LenB(arrData(LBound(arrData)))
            arrData(lPos).lID = lID
        End If
        oCB.ProgressChange lCount
        Next lCount
End Sub

Conclusion

From this discussion, we can safely state that CopyMemory is a great tool for speeding up the processing of data in Visual Basic, but it is also a two-sided sword as it can make the program crash with a beautiful Access Violation error. Therefore, it is very important to follow the guidelines exposed in order to avoid major headaches.

It can also be concluded that a solid concept of a pointer is required in order to facilitate the use of CopyMemory and other memory-related API functions.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.” - Antoine de Saint Exupéry