Library tutorials & articles

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.

Comments

  1. 26 Jun 2004 at 07:49

    Thanks. Definately some good information, however...


    It is not very practical to use the ReDim statement each iteration of a loop since it copies the entire array to a different memory location in order to change the dimension. In fact, using the CopyMemory function for this purpose is useful as well. A good compromise would be to redimension the array every 20th iteration and then check for empty array values, such as:


    If SubDirCount = UBound(DirList) Then ReDim Preserve DirList(UBound(DirList) + 20)


    an even better solution is to use an array object.

  2. 01 Mar 2003 at 13:25

    I just tested and yes, you are correct.


    Private Type MyType
       var1 As Integer
       var2 As Byte
       var3 As Boolean
    End Type


    The above type will get padded to a WORD, not a DWORD.  However, starting with a WORD is not enough.  The following will get DWORD-aligned, meaning you must not have DWORDS or QWORDS.


    Private Type MyType
       var1 As Integer
       var2 As Byte
       var3 As Long
    End Type


    Private Type MyType
       var1 As Integer
       var2 As Byte
       var3 As Double
    End Type


    Private Type MyType
       var1 As Integer
       var3 As Double
    End Type


    Thanks for the observation.  I will update the article to add the finding.

  3. 01 Mar 2003 at 11:03

    I'm willing to bet that if he first variable in the UDT had been an integer, the following Bytes would have been padded to WORD size and no more.


    This being the start of the week-end, I'll play with this later...

  4. 01 Jan 1999 at 00:00

    This thread is for discussions of CopyMemory and Arrays: Proper Use.

Leave a comment

Sign in or Join us (it's free).

Jose Pablo Ramirez Vargas

Related discussion

Related podcasts

  • Christian Beauclair

    14 mai 2008 (�mission #0074) ::.Christian Beauclair: Stratégies de migration VB6 vers .NET Nous discutons avec Christian Beauclair des stratégies de migration VB6 vers .NET. Entre autres, nous discutons comment utiliser le "VB 6 Code Advisor" et le "Interop Forms Toolkit" pour ajouter la puiss...

Want to stay in touch with what's going on? Follow us on twitter!