malloc in VB?
A COM object you can use in VB providing equivalent functions to C runtime's malloc, free and delete
Of course you're not supposed to allocate your own memory in Basic. Don't be so silly, what are you thinking of?
And you should stop using that CopyMemory call and certainly those pointers should not be there at all.
Just in case you're not feeling like any sort of Basic purist, this article is here to show you how to create little chunks of memory completely independently of variables or classes, how to access their contents, reallocate them and free them.
Just don't tell your boss that this is what you're doing...
What's It Good For?
This code has got three main uses:
- For direct translation of C code samples using the C run-time calls alloc and free
- Building structures which will store large amounts of data and are otherwise very difficult to manage in Basic, for example Hash Tables, Skip Lists, Markov Chains and so on.
- Storing data when only a long variable (pointer) is available to refer to the data, such as in API controls.
Of course, it is also useful whenever you have to allocate memory which needs to be shared with the Shell, for example with Namespace extensions (surely someone has to rise to the challenge of doing this in VB sometime?)
Getting a Malloc Object
IMalloc is a COM interface which defines the methods and properties a COM Malloc object will support. Anyone is free to design their own implementation of this interface and share it between objects. One such implementation is provided by the Windows Shell, and you can get it using the ShGetMalloc call.
The first problem is how to use this call. The API declaration can be written like this:
Private Declare Function SHGetMalloc Lib "shell32" (ppMalloc As Object) As Long
You could just pass a standard VB Object variable into the ppMalloc variable, but that means you end up with an untyped object which will refer to the Shell's Malloc object through the comparatively slow late-binding (Automation) method. In addition you don't get any design-time auto-complete nor do you get any compile-time syntax checking.
A much better way to use this object is through a Type Library. Within a Type Library you can define the IMalloc interface so you can then use IMalloc as if it were a predefined object in Visual Basic. You can then rewrite the API call to get early-binding and all the advantages that go with that:
Private Declare Function SHGetMalloc Lib "shell32" (ppMalloc As IMalloc) As Long
Remember that Type Libraries are only required when working in the VB IDE. You don't have to ship them with any binaries you build from VB, because VB will already have compiled in the Type Library information.
This sample, and other samples at this site, use Brad Martinez's IShellFolder Extended Type Library (v1.2), however there are other type libraries available containing the IMalloc interface, notably Win.TLB and WinU.TLB shipped with Bruce McKinney's Hardcore VB. I chose this type library simply because it is smaller than the Hardcore VB versions size, making it a more reasonable download!
Now to get the IMalloc interface you use this code:
Private Declare Function SHGetMalloc Lib "shell32" (ppMalloc As IMalloc) As Long
' Defined as an HRESULT that corresponds to S_OK.
Private Const NOERROR = 0
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)
' Returns a reference to the IMalloc interface.
Public Function isMalloc() As IMalloc
Static im As IMalloc
If (im Is Nothing) Then
If Not (SHGetMalloc(im) = NOERROR) Then
' Fatal error
Err.Raise 7
End If
End If
Set isMalloc = im
End Function
The isMalloc property is made public and put in a module to encourage all code in the same project to use the same instance of the object.
Allocating and Freeing Memory
Once you have a Malloc object you can then start creating and destroying memory at will! The following table summarises the available methods:
Alloc |
Allocates a block of memory of the specified number of bytes, returning a pointer to the memory.
lPtr = isMalloc.Alloc(24) ' Allocates 24 bytes
|
DidAlloc |
Returns true if the specified pointer was created using this instance of the Malloc object.
b = isMalloc.DidAlloc(ByVal lPtr) ' True if allocated
|
Free |
Frees a previously allocated block of memory
isMalloc.Free ByVal lPtr ' Frees up the memory
|
HeapMinimize |
Minimizes the heap as much as possible by releasing unused memory to the operating system.
isMalloc.HeapMinimize
|
Realloc |
Reallocates a memory block previously allocated using Alloc and returns the new pointer to it.
lPtr = isMalloc.Realloc(ByVal lPtr, 36) ' Resizes the block to 36 bytes.
|
Examples Of Use
The following examples demonstrate how to move data from Visual Basic data types to a pointer allocated through the Malloc object:
To save and restore a Byte Array
' To Save the array b():
Public Function SaveToMemory(ByRef b() as Byte) As Long
Dim lSize As Long
Dim lPtr As Long
On Error Resume Next ' Error checking: UBound/LBound can fail.
lSize = UBound(b) - LBound(b)
If Err.Number=0 Then
lSize = lSize + 1
Else
' No Data:
lSize = 0
End If
On Error Goto 0
lPtr = isMalloc.Alloc(lSize + 4)
If Not (lPtr=0) Then
' Store the size of the array:
CopyMemory ByVal lPtr, lSize, 4
If lSize > 0 Then
' Store the array:
CopyMemory ByVal lPtr + 4, b(0), lSize
End If
End If
' Return the Value of lPtr
SaveToMemory = lPtr
End Function
' To Restore the array from the Pointer lPtr:
Public Sub RestoreFromMemory(ByVal lPtr As Long, ByRef b() as Byte)
Dim lSize As Long
Erase b
If Not (lPtr = 0) Then
' Get the size of the array:
CopyMemory lSize, ByVal lPtr, 4
If lSize > 0 Then
ReDim b(0 To lSize-1) As Byte
CopyMemory b(0), ByVal lPtr, lSize
End If
End If
End Sub
To save and restore a User-Defined Type
Provided there are no variable length strings in the type, then you can save and restore a type in exactly the same way as for a ByteArray. Just replace the last argument with Len(udtThis) where udtThis is your Type.
To save and restore a String
' To Save the String sThis
Public Function SaveToMemory(ByRef sThis as String) As Long
Dim lSize As Long
Dim lPtr As Long
' Size of sThis in Bytes:
lSize = LenB(sThis)
lPtr = isMalloc.Alloc(lSize + 4)
If Not (lPtr=0) Then
' Store the size of the string:
CopyMemory ByVal lPtr, lSize, 4
If lSize > 0 Then
' Store the Unicode String:
CopyMemory ByVal lPtr + 4, ByVal StrPtr(sThis), lSize
End If
End If
' Return the Value of lPtr
SaveToMemory = lPtr
End Function
' To Restore the array from the Pointer lPtr:
Public Function RestoreFromMemory(ByVal lPtr As Long) As String
Dim lSize As Long
Dim sThis As String
If Not (lPtr = 0) Then
' Get the size of the array:
CopyMemory lSize, ByVal lPtr, 4
If lSize > 0 Then
sThis = String$(lSize\2,0)
CopyMemory ByVal StrPtr(sThis), ByVal lPtr + 4, lSize
End If
End If
End Sub
Other VB types can be moved using similar techniques.
Sample Code
The sample code in the download illustrates four different ways to create a Stack object capable of storing strings. A Stack is a simple data structure in which there are only two operations - pushing an item onto the stack and poping it back off again. The last item to be added to the stack is the first to be retrieved.
Whilst such a simple data structure is most easily achieved using arrays (and in fact this method runs quickest for most amounts of data), allocating data using IMalloc always comes a close second and in fact beats the array method for 50,000 elements:
Collection |
20,103 |
Linked Objects |
6,190 |
Array |
3,222 |
IMalloc |
2,872 |
|