Typeless Strings
Now that you understand Unicode and pointers, you have the knowledge to understand how to hack strings and string pointers with CopyMemory. This is a little bit tricky because the type library version of CopyMemory doesn’t work exactly the same as the Declare version. At first I thought this was a bug or at least a limitation of type libraries. After studying the issue, I’m inclined to consider it a bug or at least a mistake in how the Declare statement handles As Any.
Let’s assume that we define an aliased version of CopyMemory with the following Declare statement:
Declare Sub CopyMemoryD Lib “KERNEL32” Alias “RtlMoveMemory” ( _
lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)
We’ll further assume that the type library defines the same thing with its own peculiar syntax but calls its version CopyMemoryT. You can look in WINBASE.IDL on the companion CD if you’re interested in the syntax.
sSrc = “This is text"
sDstD = “************"
sDstT = “************"
CopyMemoryD ByVal sDstD, ByVal sSrc, Len(sSrc)
CopyMemoryT ByVal sDstT, ByVal sSrc, Len(sSrc)
Debug.Print sDstD
Debug.Print sDstT
The output looks like this:
This is text
This i******
What happened here is that the Declare version, because it is written in Basic, noticed that the arguments being passed to it were Strings. It did the normal Unicode conversion, creating two temporary ANSI buffers. It copied the ANSI source to the ANSI destination, which was then converted back to Unicode. The type library version, because it is written in the language-independent IDL language, had no idea of the type being passed to it. Instead of creating ANSI buffers, it just copied the internal Unicode source to the Unicode destination. But the string length passed to it was the character count, not the byte count. Only half the characters got copied. The solution is to use LenB rather than Len so that you’ll pass a byte count.
CopyMemoryT ByVal sDstT, ByVal sSrc, LenB(sSrc)
So far, so good. But one of the reasons to use a CopyMemory with As Any parameters is so that you can also pass pointers. Let’s say some API function (GetEnvironmentStrings, to name one) returns a pointer as a Long. You might want to call CopyMemory like this:
CopyMemoryD ByVal sDstD, ByVal pSrc, Len(sDstD)
CopyMemoryT ByVal sDstT, ByVal pSrc, LenB(sDstT)
What’s going to happen here depends on whether pSrc is a pointer to a Unicode string or an ANSI string. Let’s assume that it’s a pointer to ANSI characters that you got from an ANSI API function. In the Declare version, you’ll copy from the ANSI pointer to the temporary ANSI buffer, and all will be fine when Visual Basic converts that temporary buffer back to Unicode. In the type library version, you’ll copy from the ANSI pointer to the Unicode string. The result will be a string of question marks that you’ll have to convert with StrConv.
If, on the other hand, pSrc points to a Unicode string (from a Unicode or COM API function or from StrPtr), the Declare version CopyMemoryD will copy both the characters and the zero bytes of the Unicode string into the temporary ANSI buffer, which will then be converted back to an unintelligible Basic string.
The type library CopyMemoryT will copy from Unicode to Unicode with no
conversion.
If that description leaves you scratching your head, don’t worry about it. You can bypass all this trouble by using the CopyMemory aliases in the type library: CopyMemoryStr, CopyMemoryToStr, and CopyMemoryStrToStr. Call them like this:
CopyMemory ByVal pDst, ByVal pSrc, cDst
CopyMemoryStr ByVal pDst, sSrc, cDst
CopyMemoryToStr sDst, ByVal pSrc, cDst
CopyMemoryStrToStr sDst, sSrc, cDst
These will do the right thing whether you’re using WIN.TLB or WINU.TLB.