Introduction
Some of you may think that I've got the title wrong, well... sometimes I get them mixed up, but not this time. You can do pointers in Visual Basic. There’s another nice article on “How to do pointers in Visual Basic”, and I recommend you to read it before or after reading this article if you haven't read it yet.
Undocumented Functions
Now let’s see what these undocumented functions are. Get the Object Browser window and select VBA library, right click on Object Browser and select show hidden members from the menu. Now select _HiddenModule
class, and you can see three hidden functions – ObjPtr
, StrPtr
and VarPtr
. These functions are not documented as Microsoft does not guarantee they will be available in future releases of VB.
VarPtr
- gives the memory address of a variable
StrPtr
- gives the memory address of the contents of a string
ObjPtr
- gives the memory address of the object (interface)
Surely we can view variables’ memory addresses with these functions, but something is missing. How can we set the contents of a memory address? To do that, we need the API function CopyMemory
which is in kernel32.dll.
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(Destination As Any, Source As Any, ByVal Length As Long)
This function copies a block of memory, from one memory address to another. Note that according to the declaration, Destination
and Source
are passed by reference (default is ByRef
) which means that CopyMemory
function is expecting memory addresses for the Destination
and Source
arguments. Although they are declared as ByRef
we can override it by including ByVal
in our call (override ... don't you love that word?). Finally look at that Any
data type. Any
means that we can pass “any” data type for Destination
and Source
arguments, but ultimately what is being passed is a 4-byte long memory address. Keep this in mind. It's very important when working with Win32 API.
Using VarPtr
A pointer is a variable which contains the address in memory of another variable. In Windows, it takes 4 bytes to hold a memory address. So if we want to declare a pointer in VB, we must use the Long
data type.
Take a look at this code sample:
Dim myInt As Integer
Dim ptr As Long
ptr = VarPtr(myInt) Debug.Print ptr
CopyMemory ByVal ptr, 123, 2
Debug.Print myInt
If you run this code, the value 123 will be copied to the memory address of myInt
, so at the end myInt
will contain the copied value 123.
Now take a closer look at this line:
CopyMemory ByVal ptr, 123, 2
Variable ptr
is a pointer to myInt
, in other words ptr
holds the memory address of myInt
. So if we write this without ByVal
keyword, what happens? The memory address of ptr
will be passed instead of the memory address of myInt
because Destination
is declared as ByRef
(default).
Without any hassle, we can re-write this like the following:
CopyMemory myInt, 123, 2
So myInt
is passed as ByRef
, in other words the memory address of myInt
is passed, which is exactly what we want to do. Remember this small tip: When the memory address that you want to pass is in a variable, you must use ByVal
keyword to override the ByRef
.
If we want to copy the value of variable intA
to variable intB
we can do it by writing either:
CopyMemory ByVal VarPtr(intB), ByVal VarPtr(intA), 2
or:
CopyMemory intB, intA, 2
Hope you don't have any doubts about CopyMemory
or VarPtr
now. Let's move on to StrPtr
.
Using StrPtr
Did you ever wonder how the Len(str)
function works. Well, you might guess it counts the characters from the beginning of string
until a null
character is found. Guess again, this is not the way VB handles string
s. When we declare a variable of type String
we are declaring a member of a data type called BSTR
which stands for Basic String. BSTR
is a pointer to a Unicode (2-bytes per character) character array that is preceded by a 4-byte length field and terminated by a single 2-byte null character. Look at the below figure to get a better understanding.
So when we write the code:
Dim str as string
str = "hello"
Variable str
is actually a member of type BSTR
which is holding the memory address to the beginning of actual Unicode character array. Notice that it is not pointing to the 4-byte length field. This length field holds the number of bytes excluding the terminating null. So "hello"
takes 10 bytes when represented in Unicode.
Dim str As String
Dim length As Long Dim ptrLengthField As Long
str = "hello"
Debug.Print StrPtr(str)
ptrLengthField = StrPtr(str) - 4
CopyMemory length, ByVal ptrLengthField, 4
Debug.Print length Debug.Print Len(str)
Before you run this code, let's clear things out. Examine this line:
ptrLengthField = StrPtr(str) - 4
StrPtr(str)
gives the address to the character array, so we have to reduce 4 bytes to get the address to the length field.
Take a look at this line:
CopyMemory length, ByVal ptrLengthField, 4
The variable ptrLengthField
is holding the memory address to the length field of str
, so we have to use the ByVal
keyword to pass it by value.
Now run the code and the output of:
Debug.Print length Debug.Print Len(str)
will be:
10
5
Variable length
is set to 10
, because of Unicode, "hello"
takes 10 bytes, and Len(str)
returns 5
which is the number of characters in "hello"
. Now let’s find out how Len()
function really works!
Uncomment this line:
CopyMemory ByVal ptrLengthField, 200&, 4
What happens here is the value 200
is copied to the length field of str
replacing the original value of 10
. The &
sign in 200&
is there to say that treat 200
as a Long
(type declaration character for Long
is &
) .
Run the code, and the output of:
Debug.Print length Debug.Print Len(str)
will be:
200
100
We get 100
for Len(str)
when str
contains "hello"
, huh ? So VB has returned us the value of length field divided by 2. I think now you know how Len()
works for string
s.
Using ObjPtr
Finally we'll see ObjPtr
in action.
Dim obj As New Form1
Debug.Print ObjPtr(obj) Debug.Print VarPtr(obj)
There’s nothing to explain here. Here’s another simple code sample.
Dim objA As New Form1
Dim objB As New Form1
Debug.Print "before"
Debug.Print ObjPtr(objA)
Debug.Print ObjPtr(objB)
Set objA = objB
Debug.Print "after"
Debug.Print ObjPtr(objA)
Debug.Print ObjPtr(objB)
The output will be:
before
1849600
1761448
after
1761448
1761448
As you can see, after the line:
Set objA = objB
objA
now points to the same memory location as objB
, as it should be. You can also replace the above line with the following:
CopyMemory ByVal VarPtr(objA), ByVal VarPtr(objB), 4
or the much simpler version:
CopyMemory objA, objB, 4
It works, but don't use this kind of coding to set objects because sometimes an illegal operation is thrown when objects are being destroyed.
That's it. These functions are used in fast string
handling routines and subclassing. I am not going to talk about them here, 'cuz .. err ... I don't have a clue about them. Remember ... I can only show you the door, you are the one that has to walk through it!
Happy coding !!!
History
- 18th December, 2003: Initial post