Historical Perspective

The VarPtr function has been in the BASIC language since long before it turned into QuickBasic and Visual Basic. VarPtr has been in the VB runtime since version 1.0. The function can be called through a Basic declare statement pointing back into the VB runtime Dll.

   Declare Function VarPtr Lib "vbrun100.Dll" (Var As Any) As Long

Over the years, vbrun100.Dll has turned into msvbvm50.dll, but the entry point is still there. In order to get the address of a variable, simply pass the variable name to VarPtr:

   Dim l As Long
   Debug.Print VarPtr(l)

Similarly, to get the pointer to a string instead of the variable holding the string, pass the string variable ByVal instead.

   Dim s As String
   Debug.Print VarPtr(s), VarPtr(ByVal s)

Getting the pointer to a string buffer with this method was in common use through VB3, but hit a brick wall with VB4.

ANSI/UNICODE Glitch

With the arrival of the 32-bit world and VB4, we entered a Windows world which was suddenly half UNICODE and half ANSI instead of predominantly ANSI. All VB strings are stored in UNICODE, but all API calls are made with ANSI strings. This is accomplished by converting any string passed to an API call to ANSI before the call and back to UNICODE afterwards. While this conversion is transparent to the user most of the time, it makes it impossible to pass a UNICODE string from VB to a DLL via an argument typed As String in a Declare statement. Similarly, any structure which contains strings will go through the double conversion process during an API call.

How does this affect the VarPtr function? When a string is passed ByVal/ByRef to the VarPtr function shown in the Declare statement, the address returned is the address of the temporary ANSI string/variable holding the temporary ANSI string. In other words, it isn't the address of the variable you've declared anymore, so the VarPtr function accessed through a declare statement is totally useless for string variables or variables for structures containing strings.

VB5 to the Rescue

To once again enable advanced programming with VarPtr, VB5 (and Office97) added 3 entry points to the VBA type library which provide built-in declarations for the VarPtr function. To maintain the traditionally eclectic feel of VarPtr, and to stop beginning programmers from hurting themselves (read: no one wanted to document them), these declarations remain hidden. Since functions in a type library can be declared such that VarPtr returns the address of any variable (except for variables containing arrays, see October Visual Basic Programmer's Journal for more). VarPtr returns the address of a variable. StrPtr returns the address of the real UNICODE string buffer. ObjPtr returns the address of any object variable reference. Common uses for each of these functions is described below.

StrPtr

StrPtr is used primarily to make efficient UNICODE API calls. In VB4, API calls to a UNICODE function were made with the help of byte arrays:

   Declare Sub MyUnicodeCall Lib "MyUnicodeDll.Dll" _
      (pStr As Byte)

   Sub MakeCall(MyStr As String)
      Dim bTmp() As Byte
      bTmp = MyStr & vbNullChar
      MyUnicodeCall bTmp(0)
      MyStr = bTmp
      MyStr = Left$(MyStr, Len(MyStr - 1))
   End Sub

Clearly, the amount of work to simply pass a NULL terminated UNICODE string and return its value into the original string is totally unacceptable. By the time correct handling of the terminating NULL character is included, No less than 4 copies of the string are required.

With StrPtr, this code reduces to:

   Declare Sub MyUnicodeCall Lib "MyUnicodeDll.Dll" _
      (ByVal pStr As Long)

   Sub MakeCall(MyStr As String)
      MyUnicodeCall StrPtr(MyStr)
   End Sub

VarPtr/StrPtr/ObjPtr are all very, very fast, so the overhead to call a UNICODE function is now actually lower than calling the corresponding ANSI function because no conversion is required.

StrPtr can also be used to optimize ANSI declare calls. Instead of passing the same string variable multiple times to a declare function and incurring the conversion overhead for each call, use the StrConv function once and StrPtr in each call:

   Declare Sub MyAnsiCall Lib "MyAnsiDll.Dll" _
      (ByVal pStr As String)

   MyAnsiCall MyStr

becomes:

   Declare Sub MyAnsiCall Lib "MyAnsiDll.Dll" _
      (ByVal pStr As Long)

   MyStr = StrConv(MyStr, vbFromUnicode)
   MyAnsiCall StrPtr(MyStr)
   MyStr = StrConv(MyStr, vbUnicode) ' Not always required

StrPtr is also the only way to tell the different between an empty string ("") and a null string (vbNullString). StrPtr(vbNullString) returns 0, while StrPtr("") is non-zero.

VarPtr

VarPtr can be used with API calls which require structures with UNICODE strings in them. Passing MyUDTVariable to a ByRef UDTParam As MyUDT parameter will always do ANSI/UNICODE conversion. However, passing VarPtr(MyUDTVariable) to a ByVal UDTParam As Long parameter will leave all data as is and pass the structure directly.

To see other programming techniques available with the VarPtr function, refer to any C/C++ program. However, a word of caution is required here. Pointer arithmetic in VB is non-trivial. Besides calculating the element size, which isn't done for you in VB, you also have to deal with the lack of an unsigned long data type. The following function will perform unsigned arithmetic (positive increments only)

   Function UnsignedAdd(ByVal Start As Long, _
      ByVal Incr As Long) As Long

      Const SignBit As Long = &H80000000
      UnsignedAdd = (Start Xor SignBit) + Incr Xor SignBit
   End Function

ObjPtr

ObjPtr returns the pointer to the interface referenced by an object variable. Since most objects actually support multiple interfaces, it is important to get the correct interface when looking at the address of an object. With a VB declare design to take a generic object, there is no way to guarantee that the interface received by the Dll will the same as the one passed. For example, for a VB5 created Class1:

   ' Requires a stdole (OLE Automation) reference
   Declare Function VBObjPtr Lib "msvbvm50.dll" _
      Alias "VarPtr" (ByVal pObj As IUnknown) As Long

   Dim Cls1 As New Class1
   ' These value will be different:
   Debug.Print ObjPtr(Cls1), VBObjPtr(Cls1)

The most common use of ObjPtr I've found is for indexing objects placed in a collection. By creating a key based on the address of the object, it is possible to remove an object from a collection without walking the entire collection and using the Is operator to find a match, then calling the Remove method, which walks the collection again. In many cases, the address of an object is the only thing you can reliably use as a key. For example, the Name and Path properties of a Workbook object in Excel can change at any time, so these can't be used as a key.

   ObjColl.Add MyObj1, CStr(ObjPtr(MyObj1))
   ...
   ObjColl.Remove CStr(ObjPtr(MyObj1))

-- Matthew Curland