malloc in VB?

A COM object you can use in VB providing equivalent functions to C runtime's malloc, free and delete

iMalloc Graphic

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:

  1. For direct translation of C code samples using the C run-time calls alloc and free
  2. 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.
  3. 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:

Method Description
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:

Method Time
Collection 20,103
Linked Objects 6,190
Array 3,222
IMalloc 2,872