Visual Basic 6 Win32 API Tutorial, Page 3
There's an API call that comes in really handy when you need to move blocks of memory around. It's called CopyMemory:
Declare Sub CopyMemory Lib "kernel32" _ Alias "RtlMoveMemory" (lpvDest As Any, _ lpvSource As Any, ByVal cbCopy As Long)
This procedure allows you to copy memory from one location to another. It does this by reading memory from a source (lpvSource) and copying it to another location in memory (lpvDest). The size of the copy is determined by cbCopy.
This procedure has been used by many VB programmers to solve a number of problems. I don't have the space to go over each application of the call, but if you use it, beware of the first two parameters. The first two characters of the argument names should point out (no pun intended) that these are pointers to blocks of memory. Furthermore, the declaration is made with the arguments As Any. The Any keyword is not a data type, like Variant; rather, it's used to let virtually any data type into the function call (it's analogous to the void keyword in C). This makes the call extremely flexible, since it will take virtually anything you pass into it. You can copy memory from longs to longs, from arrays to arrays, and so on. However, by doing this, you've left yourself wide open to a lot of errors. For example, what happens if you pass in a pointer to a string as the source, a pointer to a long as the destination, and set the length of the string as cbCopy? It may work, but I would never bet on it. You have to make sure what kind of information will go where, and if it will make any sense once it gets there.
It's even worse with strings. As we saw before, we have to pass in the string ByVal to force VB into passing a pointer to the first character in the string. The same holds true in this case. If we have a pointer to the string, it may seem like we could just pass in the value of this pointer ByRef. However, this would cause a memory exception, since we're not adhering to what VB is doing for us underneath the scenes.
Let's create an application called TestAPIStringReturn. This program simply makes a call to GetEnvironmentStrings and puts that information into a VB string. First off, before we jump into the code, we need to initialize the project. Add a module called StringLib, and add the CopyMemory declaration I gave before to this module. However, the GetEnvironmentSetting declaration, which should also be added to the StringLib module, has to change a bit:
Declare Function GetEnvironmentStrings Lib "kernel32" _ Alias "GetEnvironmentStringsA" () As Long
Also, add a form to the project with one command button. Change the property values in this project according to the following table:
|Form||frmStringReturn||Test API String Return|
|Command Button||cmdAPIGet||Call API|
It's not a very complex project, as the following screen shot shows:
Here's the call that uses the CopyMemory procedure to get the information returned to use via a pointer from GetEnvironmentStrings. The following code should be typed into the code window of your form:
Private Sub TestStringRet() Dim lngRet As Long Dim strDest As String lngRet = GetEnvironmentStrings strDest = String$(1000, " ") CopyMemory ByVal strDest, ByVal lngRet, Len(strDest) - 1 Debug.Print strDest End Sub
We call this function from the Click event of cmdAPIGet:
Private Sub cmdAPIGet_Click() TestStringRet End Sub
Let's take a look at what this method is doing in more detail. First, we get the pointer to the string via GetEnvironmentStrings:
lngRet = GetEnvironmentStrings
Then, we allocate space for our destination buffer strDest using the String$ function.
strDest = String$(1000, " ")
Next, we use the handy CopyMemory procedure to copy 1000 bytes from the string pointed at by lngRet to strDest:
CopyMemory ByVal strDest, ByVal lngRet, Len(strDest) - 1
Finally, we print out the contents of strDest to the Debug window:
Depending upon the length of the environment string, you may end up with a bunch of null characters in strDest. I won't demonstrate it here, but you could use string functions like Instr$, Left$, and Mid$ to find the first occurrence of a null and take off that character and any others that follow it.
You'll notice that both pointer parameters were passed in ByVal, even though the declaration says the parameters are going to be used by reference. This is good, since we're passing in pointers to strings as our values. If we made the call this way:
CopyMemory strDest, lngRet, Len(strDest)
we'd throw a memory exception (trust me, I've tried it - if you really must have a go yourself make sure that you save everything first). Also, you may have wondered why I arbitrarily buffered strDest with 1000 space characters. Since we don't know the length of the string that lngRet is pointing at, I just picked a number that I thought would be large enough to handle the result. Obviously, if you wanted to use this function in a production application, you'd have to be more creative to make sure you're getting all of the environment information into strDest.
The reason we could get this to work in the first place is because we handled the data types correctly on our end. By declaring the data type As Any in the API call, you're forced to make sure that the data you're copying from can be stored in the destination location you've given. If we take another look at the FindWindow procedure, you'll notice that VB wouldn't have allowed a Long to be passed into either of its arguments:
Declare Function FindWindow Lib "user32" _ Alias "FindWindowA" _ (ByVal lpClassName As String, _ ByVal lpWindowName As String) _ As Long
VB won't do a type check when you use As Any, you're on your own. However, by passing in two strings ByVal, the call is successful.
I'd suggest that you always type your arguments whenever possible. It makes your VB coding life a lot less painful. Sure, there are exceptions - like the one we just covered - but make sure you know what's going into your code, because VB is going to drop the ball on those data types.