Motivation
Many programmers write software that require the set of running processes to be enumerated.
Unfortunately there is no standard method of doing this.
On Windows 95 and Windows 98 there are the ToolHelp API functions (found in Kernel32.dll). For some reason the Microsoft NT team didn't like the ToolHelp
functions and decided not to add them to Windows NT. Instead they provided their own set of Process Status functions, the PSAPI, and added them to an
external module (residing in psapi.dll). Finally, on Windows 2000, the developers chose to provide both methods of enumeration.
What this is
CEnumProcess
is a simple class for enumerating running processes using either PSAPI or ToolHelp. The preferred method is decided on runtime.
It contains two classes:
CEnumProcess::CProcessEntry
and
CEnumProcess::CModuleEntry
for storing results.
How it works
When creating an instance of the class, it attempts to load the appropriate modules and find PSAPI/ToolHelp related functions. Based on which
set of functions it finds the class sets the enumeration method to the most appropriate. For example, this is the code for finding the PSAPI related functions:
PSAPI = ::LoadLibrary(TEXT("PSAPI"));
if (PSAPI)
{
FEnumProcesses = (PFEnumProcesses)::GetProcAddress(PSAPI,
TEXT("EnumProcesses"));
FEnumProcessModules = (PFEnumProcessModules)::GetProcAddress(PSAPI,
TEXT("EnumProcessModules"));
#ifdef UNICODE
FGetModuleFileNameEx = (PFGetModuleFileNameEx)::GetProcAddress(PSAPI,
TEXT("GetModuleFileNameExW"));
#else
FGetModuleFileNameEx = (PFGetModuleFileNameEx)::GetProcAddress(PSAPI,
TEXT("GetModuleFileNameExA"));
#endif
}
Usage
There are seven public functions in the class:
int GetAvailableMethods()
int GetSuggestedMethod()
int SetMethod(int method)
BOOL GetProcessNext(CProcessEntry *pEntry)
BOOL GetProcessFirst(CProcessEntry* pEntry)
BOOL GetModuleNext(DWORD dwPID, CModuleEntry* pEntry)
BOOL GetModuleFirst(DWORD dwPID, CModuleEntry* pEntry)
The enumerationmethod related functions sets/returns one of the values found in the namespace
ENUM_METHOD
below:
namespace ENUM_METHOD
{const int NONE = 0x0;
const int PSAPI = 0x1;
const int TOOLHELP= 0x2;
const int PROC16 = 0x4;
}
ENUM_METHOD::NONE
is used for the unlikely event that no method can be found, like if an NT-user has deleted psapi.dll.
Under Windows 2000, GetAvailableMethods
returns ENUM_METHOD::PSAPI + ENUM_METHOD::TOOLHELP
. The suggested method will be
to use the ToolHelp API. If 16-bit processes should be enumerated ENUM_METHOD::PROC16
should be added to the enumerationmethod. This is usually done
by default.
More interesting are the GetProcess/GetModule functions. They take a pointer to a CProcessEntry
/CModuleEntry
as input, and
returns TRUE
/FALSE
depending on success or failure in enumeration. The useful members of the classes are as follows:
CProcessEntry
LPTSTR lpFilename;
DWORD dwPID;
WORD hTask16;
The member hTask16
is only used on NT and Win2k. If hTask16
is anything other than 0 on these operating systems, this is a 16-bit process. In that case lpFilename
will be the path of the 16-bit process, and
dwPID
will be the identifier of the NTVDM currently run under (see "A WORD on 16-bit processes").
Note that if using ToolHelp under Win2k lpFileName
will not be the full path of the file. Try using GetModuleFirst
to get the first module
loaded (the .exe itself), and then retrive the full path from CModuleEntry
.
CModuleEntry
LPTSTR lpFilename;
PVOID pLoadBase;
PVOID pPreferredBase;
All this means you can write code like this, and it will work under both Win9x and NT.
CEnumProcess enumeration;
CEnumProcess::CProcessEntry entry;
for (BOOL OK = enumeration.GetProcessFirst(&entry); OK;
OK = enumeration.GetProcessNext(&entry) )
{
TRACE("PID = %X, process = %s\n", entry.dwPID, entry.lpFilename);
}
Why the preferred base is important
I have found that many modules does not load at their preferred baseaddress. If a module does not load at its preferred base, the application
using it will require more memory and take a performance hit when initializing. This is because a module can only be mapped to its .dll on disk if the baseaddress is equal to the loadaddress.
This was the reason for including the preferred base in
the moduleentry in the first place. Usually is it due to lazy programmers, who do not rebase their modules from the the default
address (0x10000000, or Visual Basic 0x11000000). It could also be hard to predict where a module can find some place to load.
The function for finding the preferred base is found below:
PVOID CEnumProcess::GetModulePreferredBase(DWORD dwPID, PVOID pModBase)
{if (ENUM_METHOD::NONE == m_method) return NULL;
HANDLE hProc = OpenProcess(PROCESS_VM_READ, FALSE, dwPID);
if (hProc)
{IMAGE_DOS_HEADER idh = {0};
IMAGE_NT_HEADERS inh = {0};
ReadProcessMemory(hProc, pModBase, &idh, sizeof(idh), NULL);
if (IMAGE_DOS_SIGNATURE == idh.e_magic)
ReadProcessMemory(hProc, (PBYTE)pModBase + idh.e_lfanew,
&inh, sizeof(inh), NULL);
CloseHandle(hProc);
if (IMAGE_NT_SIGNATURE == inh.Signature)
return (PVOID) inh.OptionalHeader.ImageBase;
}
return NULL;
}
For those new to the PE format, these are standard headers of an executable file. When an executable
is loaded into memory, the entire file is mapped - even the headers. A lot of useful information can be found here,
like the size of the mapped file.
A WORD on 16-bit processes
Under Windows 95 and Windows 98 ToolHelp API treats 16-bit applications just like any other process. This is not the case under NT.
Instead
CEnumProcess
will return the name of the NT virtual DOS machine (NTVDM) it is currently run under. This means that
if you run the testapplication and find some processnames like "ntvdm.exe", you have found a 16-bit process. If
ENUM_METHOD::PROC16
is currently set, the next entries returned by
GetNextProcess
will be the 16-bit processes that currently run in this virtual machine.
This is done by using the VDMDBG API - a set of functions for debugging 16-bit applications. VDMDBG allows enumeration of modules, but only if the process is acting as a debugger. This require that the 16-bit debugger WOWDEB.EXE task runs in the
VM currently debugged. Attaching as a debugger is not a very good idea, though. There is no way of unattaching and if
the debugger process terminates so will the debugee. For that reason enumeration of 16-bit modules is not included in the class.
Known bugs/limitations
Some processes has a sequrity attribute that prevents reading its memory. This means that modules
can not be enumerated. If using PSAPI, not even the filename can be retrieved.
Contact
Send suggestions, improvements and comments to
me.
History
10 Sep 2002 - updated downloads