The process list—Windows NT view


When CreateProcessList is called under Windows NT, it calls the EnumProcesses function to get a list of all the processes in the system. PSAPI.DLL provides EnumProcesses and a variety of other functions for iterating through modules, threads, and more esoteric system data. This DLL isn’t part of Windows NT, but it is a standard, redistributable tool provided with the Win32 Software Development Kit. It is also provided on the CD of this book. The DLL works by reading the Windows NT performance database, which is part of the registry. With
previous versions of Windows NT, programmers had to read the performance data out of the registry directly. It’s a very quirky operation that I wouldn’t have attempted in Visual Basic. Enumerating processes with PSAPI.DLL is more
complicated than calling the ToolHelp32 functions, but it’s a lot easier than reading the weird registry format of the performance database.


Here’s the Windows NT half of CreateProcessList:

Else
' Windows NT uses PSAPI functions
Dim i As Long, iCur As Long, cRequest As Long, cGot As Long
Dim aProcesses() As Long, hProcess As Long, hModule As Long
cRequest = 96 ' Request in bytes for 24 processes
Do
ReDim aProcesses(0 To (cRequest / 4) - 1) As Long
f = EnumProcesses(aProcesses(0), cRequest, cGot)
If f = 0 Then Exit Function
If cGot < cRequest Then Exit Do
cRequest = cRequest * 2
Loop
cGot = cGot / 4 ' From bytes to processes
ReDim Preserve aProcesses(0 To cGot - 1) As Long

For i = 0 To cGot - 1
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION Or _
PROCESS_VM_READ, 0, _
aProcesses(i))
' Ignore processes that fail (probably no
' security rights)
If hProcess = 0 Then GoTo NextFor
' Get first module only
f = EnumProcessModules(hProcess, hModule, 4, c)
If f = 0 Then GoTo NextFor
sName = String$(cMaxPath, 0)
c = GetModuleFileNameEx(hProcess, hModule, sName, cMaxPath)
' Put this process in vector and count it
Set process = New CProcess
process.Create aProcesses(i), Left$(sName, c)
iCur = iCur + 1
Set vec(iCur) = process
NextFor:
Next
End If
Set CreateProcessList = vec
End Function

This code starts off by reading the process IDs for all the processes into an array. This is equivalent to calling CreateToolhelp32Snapshot under Windows 95. Unfortunately, there’s no way to determine the appropriate size of the array except trial and error. I made my initial guess large enough to handle all my processes with one pass through the loop, but you might keep your system even busier than mine.


Once you’ve got process IDs, the next step is to convert them into handles with OpenProcess. This is a normal Win32 function supported by both versions of Windows. It’s in the Windows API type library, so there’s no need for a Declare statement. We’ll see OpenProcess again in Chapter 11. Once you have a process handle, use it to get a module handle with EnumProcessModules—the function used later to build a module list. In this case, we need only the first module, which in Windows NT is always the main process module.

With the process and module in hand, it’s time to get the module name with Get­ModuleFileNameEx. You might remember GetModuleFileName from 16-bit Windows. It still exists in 32-bit windows, but unlike the 16-bit version, it requires a real module handle rather than an instance handle, and even then it fails for all processes other than the current one. GetModuleFileNameEx works for any process, but it requires both the process and the module handle.

FLAME The loop in CreateProcessList is a perfect illustration of why Basic programmers need the Continue statement enjoyed by C, C++, and Java programmers. Structured programming fanatics will object to my use of GoTo to skip to the next iteration when a process is discovered to be inaccessible, but two additional levels of nesting would be even more unstructured in my view.


The CreateProcessList function is complicated so that the RefreshProcessList procedure of WinWatch can be simple:

Private Sub RefreshProcessList()
Dim processes As CVector, process As CProcess, i As Long
Set processes = CreateProcessList
SetRedraw lstProcess, False
lstProcess.Clear
For i = 1 To processes.Last
lstProcess.AddItem processes(i).EXEName
lstProcess.ItemData(lstProcess.NewIndex) = processes(i).id
Next
SetRedraw lstProcess, True
End Sub