Bring Your Hatchet
Dealing with pointers in Basic is kind of like getting in a hatchet fight without a hatchet. If you’re going to hack into places where you’re not supposed to go, you’d better arm yourself appropriately. The weapons of the well-equipped Basic pointer hacker are these: CopyMemory, VarPtr, StrPtr, and ObjPtr.
Of these, CopyMemory is the weapon of choice when you want to chop through Basic type limitations. The sidebar on the following page tells the bizarre history of CopyMemory, which isn’t really named CopyMemory. Here, we’re more interested in what it can do. For example, if you’ve ever tried manipulating bit fields in Visual Basic, you know that Basic’s lack of unsigned types and strong typing can make simple operations difficult. Here’s how you have to write a simple function that extracts the low Integer of a Long:
Function LoWord(ByVal dw As Long) As Integer
If dw And &H8000& Then
LoWord = dw Or &HFFFF0000
Else
LoWord = dw And &HFFFF&
End If
End Function
It’s not particularly readable, but as we’ll see in Chapter 5, it is efficient. We can make this code simpler with CopyMemory. Ignore Basic’s picky requirements and simply blast the bits you want to wherever you want them:
Function LoWord(ByVal dw As Long) As Integer
CopyMemory LoWord, dw, 2
End Function
To see exactly how this works, let’s pretend for a moment that CopyMemory is written in a dialect of Basic that doesn’t yet exist. It looks something like this:
Sub CopyMemory(anyDestination As Any, anySource As Any, _
ByVal c As Long)
Of course there’s not really an Any type in Basic, and most of the time you wouldn’t want a feature so dangerous—although it’s nice to have it as an out in API functions. The first parameter of CopyMemory is a ByRef parameter (the address) of a variable of any type that you’ll write some bytes to. The second parameter is the ByRef variable you’ll copy from. The third parameter is the number of bytes to copy. If your finger slips and you type 20 instead of 2 in your CopyMemory call, nobody will complain until crash time.
What good is a LoWord function without a HiWord function? But this one gets a little more complicated.
CopyMemory: A Strange and Terrible Saga
Here’s the long, strange story of how the Win32 function for copying raw memory came to be called CopyMemory, even though there’s no such function in Visual Basic or in the Windows API.
It started when I first began searching for the Win32 equivalent of the Win16 hmemcpy function for use in Visual Basic version 4. No such thing—not even a note that the function might be obsolete. But…
The closest I could come up with was the CopyMemory function, which has exactly the same arguments and is documented the same as the old hmemcpy. Unfortunately, despite what you might read in Win32 documentation, there is no such thing as CopyMemory. You can search all the 32-bit DLLs with the DumpBin utility, but you won’t find any DLL containing CopyMemory. But…
If you search carefully through the Win32 C include files, you’ll turn up the following in WINBASE.H:
#define CopyMemory RtlCopyMemory
#define MoveMemory RtlMoveMemory
#define ZeroMemory RtlZeroMemory
This C equivalent of an alias indicates that CopyMemory is another name for a function called RtlCopyMemory. Don’t ask why; just check for RtlCopyMemory in KERNEL32.DLL. Again, nothing. More sleuthing in the Win32 include files reveals the reason. WINNT.H contains something like this:
#define RtlCopyMemory(dst, src, len) memcpy(dst, src, len)
In other words, RtlCopyMemory is an alias for the C memcpy function, but you can’t use memcpy or any other C library function from Basic. The documenta-tion is simply lying when it claims that CopyMemory is a Windows function rather than a C function. If it’s not exported from a DLL, you can’t call it. But…
KERNEL32.DLL does contain an entry for RtlMoveMemory. If you check the Win32 documentation, you’ll see that MoveMemory does the same thing as CopyMemory except that it handles overlapped memory in a different fashion. I can’t imagine a situation in which a Basic programmer would be copying overlapped memory. No reason not to use MoveMemory instead. The name CopyMemory seemed more intelligible than hmemcpy or MoveMemory, so I used this alias for both 16-bit and 32-bit versions:
#If Win32 Then
Declare Sub CopyMemory Lib “KERNEL32” Alias “RtlMoveMemory” ( _
lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)
#Else
Declare Sub CopyMemory Lib “KERNEL” Alias “hmemcpy” ( _
lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)
#End If
The Windows API type library has an equivalent (or almost equivalent) CopyMemory function.
That explains why I used CopyMemory, but why does everybody else use it? Because I sent a copy of my sidebar to an internal alias at Microsoft, and someone who read it decided it would make a good Knowledge Base article. I agreed to let them use it if they mentioned it was an excerpt from my book. Good advertising, I thought. Ever since then I’ve read articles and heard speakers at the VBITS conference talking about CopyMemory as if it really existed. And none of them mention my book as the source. So don’t be fooled by false advertising. If they talk about RtlMoveMemory, they figured it out on their own. If they talk about CopyMemory, they got it (perhaps without knowing) from me.
Function HiWord(ByVal dw As Long) As Integer
CopyMemory HiWord, ByVal VarPtr(dw) + 2, 2
End Function
The destination and the size part work the same as in LoWord, but the source must be the upper two bytes of a Long, and there’s no way to specify part of a variable by reference. You have to calculate the address of that variable just like you would in assembly language. First you get the start of the variable with VarPtr, then you skip over two bytes to the middle of the variable, and finally you override CopyMemory’s normal ByRef parameter passing with ByVal.
If you remember VarPtr, you’re a real Basic old-timer. VarPtr was a function in QuickBasic, Basic Professional Development System, Macintosh Basic, GW-
BASIC, and BASICA. You pass VarPtr a variable; it returns a pointer to that variable. Street Basic squared. Obviously, Visual Basic can have no trace of this abomination. Or can it? In previous versions of Visual Basic, VarPtr was a secret known only to the most hardcore of programmers (including readers of the first edition of this book). You had to figure out its secret DLL location and write a Declare statement for it. Well, now VarPtr has come halfway out of the closet. It’s part of Visual Basic, and you don’t need a Declare statement to use it. But you won’t find any official documentation on it. If you call product support to complain about bugs in code that uses it, I expect that they will deny all knowledge.
They’ll probably also deny knowledge of VarPtr’s cousins, StrPtr and ObjPtr. VarPtr doesn’t work on Strings because the pointer you would get back would be the pointer to the ANSI buffer that Visual Basic creates when calling API functions. StrPtr returns a pointer to the real Unicode string value. ObjPtr returns a pointer to an object. It makes me dizzy to even think about what hardcore programmers might think of to do with object pointers. I won’t talk about them in this chapter.