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
|