VB6 supports three functions – VarPtr, StrPtr, and ObjPtr – that were never officially supported by Microsoft, yet they have been extensively used by many expert VB6 developers. In fact, you badly need these functions when calling some especially complex Windows API functions. Of these functions, the most useful one is VarPtr, which returns the address of the memory location where the value of a variable is stored.

Neither the Upgrade Wizard nor any other VB6 migration tool supports the VarPtr keyword, thus I decided that our VB Migration Partner *had* to correctly convert it. Thanks to Google, I found out that the problem had been discussed at length in many forums, but no definitive solution has ever been found yet. You can write an implementation in unsafe C#, or an unmanaged DLL written in C or Delphi, but these solution would force us to distribute a separate DLL with VB.NET apps converted by VB Migration Partner, which I’d rather not to.

It took a while and a lot of thinking, but in the end I figure out a way to solve the problem. Yes, it is possible to write a VarPtr function in plain VB.NET, with only the help of a method exposed by the Windows API. Actually, you need just a few lines of code:

' -----------------------------------------------------------
' VARPTR implementation in VB.NET
' Part of VB Migration Partner’s support library
' Copyright © 2009, Francesco Balena & Code Architects
' -----------------------------------------------------------

Module VarPtrSupport
   ' a delegate that can point to the VarPtrCallback method
   Private Delegate Function VarPtrCallbackDelegate( _
ByVal address As Integer, ByVal unused1 As Integer, _
unused2 As Integer, ByVal unused3 As IntegerAs Integer

   ' two aliases for the CallWindowProcA Windows API method
   ' notice that 2nd argument is passed by-reference
   Private Declare Function CallWindowProc Lib "user32" _
"CallWindowProcA" _
      (ByVal wndProc As VarPtrCallbackDelegate, ByRef var As Short, _
      ByVal unused1 As Integer, ByVal unused2 As Integer,  _
ByVal unused3 As Integer) As Integer

   Private Declare Function CallWindowProc Lib "user32" _
      Alias "CallWindowProcA" _
      (ByVal wndProc As VarPtrCallbackDelegate, ByRef var As Integer, _
      ByVal unused1 As Integer, ByVal unused2 As Integer, _
ByVal unused3 As Integer) As Integer
   ' ...add more overload to support other data types...

   ' the method that is indirectly executed when calling CallVarPtrSupport
   ' notice that 1st argument is declared by-value (this is the
   ' argument that receives the 2nd value passed to CallVarPtrSupport)

   Private Function VarPtrCallback(ByVal address As Integer, _
ByVal unused1 As Integer, ByVal unused2 As Integer, _
         ByVal unused3 As Integer) As Integer
      Return address
   End Function

' two overloads of VarPtr
   Public Function VarPtr(ByRef var As Short) As Integer
      Return CallWindowProc(AddressOf VarPtrCallback, var, 0, 0, 0)
   End Function

   Public Function VarPtr(ByRef var As Integer) As Integer
      Return CallWindowProc(AddressOf VarPtrCallback, var, 0, 0, 0)
   End Function

   ' ...add more overload to support other data types...
End Module

To understand how the trick works, let’s see what happens when you call the VarPtr method. The only line of code in this method invokes one of the overloads of CallWindowProc method. The CallWindowProc method takes five arguments, the first one of which is a delegate that must point to a method that takes four 32-bit values. CallWindowProc invokes the method pointed to by the delegate and passed the other four values to such a method.

The key point in this mechanism is that each CallWindowProc overload takes a value by-reference in its second argument – a Short and an Integer, respectively. This means that the CallWindowProc method (buried inside the User32.dll) receives the address of the Short or Integer variable. This address is a 32-bit integer and is passed verbatim to the VarPtrCallback method. This method in turn receives a 32-bit integer value with by-value semantics, which means that the address parameter now contains whatever value was pushed on the stack by the CallWindowProc method.

Let’s quickly recap: the VarPtr method pushes the address of the Short or Integer variable – that is, the value we are interested in – on the stack. This 32-bit integer is received by the CallWindowProc method (in User32.dll) and is sent to the VarPtrCallback method, which receives it in its first argument and returns it verbatim to the CallWindowProc method, which in turn returns it to the VarPtr method that can finally return it to the caller.

Notice that you might need to add more overloads for the VarPtr method (and the CallWindowProc method), to support data types other than Short or Integer. Just remember that you can’t use this technique with String, Objects, or other reference types. It doesn’t work with Boolean values, either.

Interestingly, you can use the VarPtr method with structures, provided that the structure doesn’t contain any String, Object, or Boolean elements. To get the address of a structure just use VarPtr on its first element, as in this example:

Structure POINTAPI
   Public x As Integer
   Public y As Integer

End Structure

Dim addr As Integer = VarPtr(pnt.x) 

To prove that the VarPtr function works correctly, let’s use it together with the CopyMemory Windows API method to delete an element in an array by quickly shifting all the elements towards lower indices:

Declare Sub CopyMemory Lib "Kernel32.dll" Alias "RtlMoveMemory" _
   (ByVal dest As Integer, ByVal source As Integer, _
    ByVal numBytes As Integer)

   Dim arr(1000) As Integer
   ' initialize the array
For n As Integer = 0 To UBound(arr) : arr(n) = n : Next
   ' ...
   ' delete first element by shifting all elements towards the beginning
of the array, then clear last element
   CopyMemory(VarPtr(arr(0)), VarPtr(arr(1)), UBound(arr) * 4)
   arr(UBound(arr)) = 0
   ' check that it worked fine
   For n As Integer = 0 To UBound(arr) - 1
      If arr(n) <> n + 1 Then Debug.Print("Wrong value at index {0}", n)

End Sub

In case you are wondering why you should use CopyMemory and VarPtr to shift all the elements of an array – instead of a plain For … Next loop – the answer is: execution speed. Under VB6 this technique was often used to significantly speed up array operations; the VB.NET compiler produces more efficient code and this technique is seldom necessary, nevertheless the ability to convert this code from VB6 without any major edits means that you don’t have to spend too much time trying to understand what the VB6 developer meant to do.

Please notice that this implementation of VarPtr works well in 32-bit applications only, and fails when running on 64-bit operating systems. To ensure that things work as expected and that 32-bit code is generated even when running on 64-bit versions of Windows, you must select the Target CPU = x86 option in the Advanced Compile Options dialog box, in the Compile tab of the My Project page. (Odds are that you have to select this option anyway when doing complex operations with pointers.)

Important warnings: working with memory addresses under .NET can be very dangerous, much more dangerous than under VB6. The reason is, the garbage collector can fire virtually anytime while the program is executing, therefore the address of an object can suddenly change and the unmanaged method (CopyMemory in above example) would receive the address of a memory area that doesn't contain the data any longer. The neat result would be either a wrong value or an application crash. When using this implementation of VarPtr under VB.NET keep the following points into account:

  1. VarPtr is absolutely safe only when used to return the address of simple local variables, such as Short, Integer, Single, Double, or Date variables. Using local variables is safe because local variables are allocated on the stack and don't move even if an unexpected garbage collection occurs immediately after the VarPtr method returns but before the unmanaged method complete its execution.
  2. Passing the element of a Structure is safe, but only if the Structure is held in a local variable (as opposed to a class field)
  3. In all other cases, VarPtr isn't  100% safe and might occasionally deliver wrong results or crash the application. For example, it isn't 100% safe to pass VarPtr a class field or an element of an array, because array elements are stored in the managed heap and can be moved by the garbage collector (regardless of whether the array is stored in a local variable). 
  4. In a single-thread application the probability that a garbage collection occurs unexpectedly are very low and might even be considered as negligible, but they can't be considered as equal to zero.
  5. You can further minimize the probability of an unexpected GC by avoiding calling methods and language functions (e.g. Left, Int, Abs) inside the call to the unamanged method but, again, you can't reduce this probability to zero.

To recap, except when you are in cases #1 and #2 above, the converted code will work most of the time, but it can't be guaranteed to work always.The only documented way to ensure that an object doesn't move in memory because of unexpected gargabe collection is by pinning the object, by means of methods exposed by the System.Runtime.InteropServices.Marshal class.

Even with this limitation, the VB.NET implementation VarPtr method is quite helpful when doing a quick-and-dirty migration - using VB Migration Partner or the Upgrade Wizard. You can use VarPtr to check that the converted code works as intended, but it is strongly recommended that you get rid of VarPtr before going to production. In the CopyMemory case see above, for example, you can do without the VarPtr by using a different overload of the CopyMemory method that takes by-reference arguments:


Declare Sub CopyMemoryByref Lib "Kernel32.dll" Alias "RtlMoveMemory" _
   (ByRef dest As Integer, ByRef source As Integer, _
    ByVal numBytes As Integer)

(This code works because the .NET Framework automatically pins every object passed by reference to an external method.)

Even better, you should do your best to avoid unamanged calls altogether and replace them with calls to methods of pure .NET objects.