|Products Purchase Publishing Articles Support Company Contact|
Products > Books > COM > Win32 API Puzzle Book and Tutorial > Sample Puzzle > Sample Puzzle Solution
Visual Basic Programmer's Guide to the Win32 API
Exploring VB6 (series)
Developing COM/ActiveX Components with VB6: A Guide to the Perplexed
What's New from the VB 5.0 edition
Win32 API Puzzle Book and Tutorial
NT Security Programming with Visual Basic 6
Visual Basic Programmer's Guide to the Windows 16 bit API
Note: This page describes a legacy product or book. The page is available for archival purposes and as a courtesy to those who have linked to it, but is no longer being updated or maintained.
Win32 API Puzzle Book and Tutorial
If you received the error message that the program can't find the psapi.dll dynamic link library, your system is suffering from a severe and unrecoverable problem - it's running Windows 95 or Windows 98. This function, like many of the coolest API functions, runs only on Windows NT. If you don't have Windows NT available you won't be able to test this program further, though you will still learn something by reading the solution that follows.
How do you go about solving a problem like this?
You could start by obtaining the detailed error information for the function by reading the LastDllError property of the Error object. This returns an error code that can be compared with the error constants in the winerror.h header file or api32.txt file included with the book. The result in this case is 6, which coresponds to the following constant:
' The handle is invalid.
Public Const ERROR_INVALID_HANDLE = 6&
Invalid handle? How can this be? The handle is being passed correctly, since it is declared ByVal As Long - so the actual value is being passed as the parameter. The handle for the current process is obtained using the GetCurrentProcessId function. or is it?
The GetCurrentProcessId function retrieves the process identifier, a number that uniquely identifies a process in the system. But is this a handle to a process?
It is not.
The GetProcessMemoryInfo function requires a handle to a process - not a process identifier. The easiest way to obtain a handle to the current process is using the GetCurrentProcess function. Modify the function call to the following:
lReturn = GetProcessMemoryInfo(GetCurrentProcess(), _ VarPtr(uMemory), Len(uMemory))
When you run the program now, it will work! The working set size is now non-zero. On my system the value when running under the VB environment came out to about 45000.
At this point, some common sense should kick in. What kind of program can have a working memory set of only 45000 bytes? Under Windows? Come on! It's just not possible.
Something else must be going on.
When you see a suspicious value (and even when you don't), it pays to take a few extra minutes and look at some of the other values in the structure.
Try modifying the code as follows to load data from the structure into a list box:
Private Sub cmdInfo_Click() Dim lReturn As Long Dim uMemory As PROCESS_MEMORY_COUNTERS lReturn = GetProcessMemoryInfo( _ GetCurrentProcess(), _ VarPtr(uMemory), Len(uMemory)) List1.Clear List1.AddItem "Structure size: " & uMemory.cb List1.AddItem "Working set: " & _ uMemory.WorkingSetSize List1.AddItem "Page file usage: " _ & uMemory.PagefileUsage List1.AddItem "Page faults: " _ & uMemory.PageFaultCount End Sub
The values I saw were as follows:
Structure size: 13393920 Working set: 46358 Page file usage: 0 Page faults: 13393920
Looks like garbage to me. But it doesn't make any sense. If we're passing the wrong parameter, how can anything be loaded into the structure? To understand the answer, look at the stack frame diagram in figure 1. I realize that if you've never seen a stack frame diagram before, this figure will be very confusing.
And given that I spend the better part of a full tutorial in the book explaining stack frames, I won't be able to provide more than a cursory description in this demo puzzle. But hopefully it will be enough for you to understand the problem.
A stack frame refers to the contents of the stack during a function call. At the bottom of the figure you can see the three parameters that were passed to the function, the process handle, the ppsMemCounters parameter, and the cb parameter that contains the size of the PROCESS_MEMORY_COUNTERS structure passed using the ppsMemCounters parameter. The BP processor register marks the separation between the function parameters and any local variables for the function. The sample program defines two local variables. The first is the lReturn long variable that is used to hold the result of function calls. The second is the uMemory parameter that contains 10 long values. There's a third one in the figure that is not defined in the sample puzzle - I'll tell you about it in a moment.
First, think about what the GetProcessMemoryInfo function expects to see in those three parameters - because it sees only what you placed on the stack during the function call. The Process parameter contains the handle of the process. The cb parameter contains the size of the structure. Clearly these both have to be declared by value in order to place the actual handle and size values on the stack, and so they are.
The ppsMemCounters parameter must be a pointer to a PROCESS_MEMORY_COUNTERS structure. How can you obtain a pointer to a structure? There are two ways: you can declare the parameter as the structure type and pass the structure directly. In that case the code would look like this:
Private Declare Function GetProcessMemoryInfo Lib _ "psapi.dll" (ByVal lHandle As Long, lpStructure As _ PROCESS_MEMORY_COUNTERS, ByVal lSize As Long) As Integer lReturn = GetProcessMemoryInfo(GetCurrentProcess(), _ uMemory, Len(uMemory))
Or you can use the VarPtr operator to obtain the address of the structure and place it on the stack, as was chosen in this example. And the sample code does, indeed, retrieve the structure address and pass it to the function. Or does it?
Remember, the declaration is currently as follows:
Private Declare Function GetProcessMemoryInfo Lib _ "psapi.dll" (ByVal lHandle As Long, lpStructure As _ Long, ByVal lSize As Long) As Integer
The lpStructure parameter is defined As Long, without a ByVal operator. That tells Visual Basic to place a pointer to the parameter on the stack, not the parameter itself. But the parameter in this case is VarPtr(uMemory) – an expression that calculates a pointer. How can you place a pointer to the result of an expression on the stack?
The only way to do this is to create a temporary variable, store the result of the expression in that variable, then pass a pointer to that variable as a parameter!
Looking again at Figure 1, the value of the ppsMemCounters field is NOT a pointer to the uMemory structure. It is, instead, a pointer to the temporary variable holding the result of the VarPtr(uMemory) operator.
The API function sees that pointer and proceeds to load the structure at that address (or what it thinks is the structure at that address) with data. In other words, that temporary variable and the next 9 long values after that are loaded by the function. No wonder garbage appeared in the structure. It was actually being overwritten unintentionally because it happens to be located right next to the temporary variable in memory.
Imagine what would happen if you had other local variables, such as strings, array indexes, variants or object variables in the function. Depending on the location of the local variables relative to the temporary variable, you would get different types of data corruption, runtime errors and even memory exceptions.
Aren't you glad you took a second look to make sure that the values retreived by the structure made sense?
Change the declaration to include the ByVal as shown here:
Private Declare Function GetProcessMemoryInfo Lib _ "psapi.dll" (ByVal lHandle As Long, ByVal lpStructure _ As Long, ByVal lSize As Long) As Integer
Now the program will work flawlessly and the data will make sense.
You can download the final project form from ftp://ftp.desaware.com/SampleCode/DemoPuzzles/frmSolution1.frm
This is what I would consider an intermediate to advanced level puzzle. Most programmers past the beginner's level would know how to use the LastDllError property to figure out that the handle was invalid, though it might take some time to find information on the difference between Process handles and Process IDs (a subject that is covered, incidently, in my Win32 API guide as well). Most programmer's would also think to focus on the structure pointer as the most likely source of problems, though they would be more likely to change the declaration to the PROCESS_MEMORY_COUNTERS type than try to puzzle out the use of the VarPtr operator. But only the more advanced VB programmers would understand how a pointer can be incorrect yet still load data into the structure (even if it is incorrect data).