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