Click here to Skip to main content
Click here to Skip to main content

Enumerating processes on NT/Win9x

, 9 Sep 2002
Rate this:
Please Sign up or sign in to vote.
A simple class encapsulating tlhelp32 and PSAPI

Sample running on Win2k

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:

// Try to load psapi.dll	
PSAPI = ::LoadLibrary(TEXT("PSAPI"));
if (PSAPI)  
{
     // Find PSAPI functions
     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;  // name of file
DWORD  dwPID;	    // ID of the process	
WORD   hTask16;	    // If this is a 16-bit process, 
                    // return task handle here, otherwise 0

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;    // path of file
PVOID pLoadBase;      // Loading address
PVOID pPreferredBase; // Address specified in fileheader

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) )
     {
         // Do something useful
         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};
  //Read DOS header
  ReadProcessMemory(hProc, pModBase, &idh, sizeof(idh), NULL);

  if (IMAGE_DOS_SIGNATURE == idh.e_magic) // DOS header OK?
      // Read NT headers at offset e_lfanew 
      ReadProcessMemory(hProc, (PBYTE)pModBase + idh.e_lfanew,
                        &inh, sizeof(inh), NULL);

  CloseHandle(hProc); 
    
  if (IMAGE_NT_SIGNATURE == inh.Signature) //NT signature OK?
   // Get the preferred base...
   return (PVOID) inh.OptionalHeader.ImageBase; 
 }
 return NULL; //didn't find anything useful..
}
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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

moliate
Web Developer
Sweden Sweden
Been interested in computers since he got his hands on an ABC 80, and finally managed to get it to play a simple tune (the ABC 80 didn't have a soundchip, just some "prerecorded" sounds you could address with assembler. It did, however, create some sweet?? music when reading a program from tape).
 
Mostly selftaught in Visual Studio, he is currently working as a freelance programmer.

Comments and Discussions

 
Generalpacket sniffer Pinmembersura_b4_u16-May-05 2:09 
Generalreference sample letter personal PinsussAnonymous7-Mar-05 13:31 
GeneralRe: reference sample letter personal PinmemberRavi Bhavnani7-Mar-05 14:47 
GeneralI could not run this program!! Pinmemberaakillaa9-Feb-05 20:28 
GeneralDrive output to a text file Pinmemberhamudi-doodi8-Dec-04 10:58 
I would like to modify your existing code to direct the output(all process names) to a text file. What is the easiest way to do so?
 
Thank you,
hamudi
GeneralRe: Drive output to a text file Pinmembermoliate12-Dec-04 11:55 
GeneralGetModuleBaseName question PinmemberTom Wright10-Feb-04 8:04 
GeneralRe: GetModuleBaseName question Pinmembermoliate10-Feb-04 10:22 
GeneralRe: GetModuleBaseName question PinmemberTom Wright10-Feb-04 16:57 
QuestionHow to enumerate threads of a process PinmemberLiPei1-Sep-03 18:06 
AnswerRe: How to enumerate threads of a process Pinmembermoliate5-Sep-03 6:45 
Generalsize of process/module Pinmemberangelo1326-Jun-03 14:16 
GeneralRe: size of process/module Pinmembermoliate26-Jun-03 22:22 
GeneralRe: size of process/module PinsussAnonymous7-Mar-05 13:31 
QuestionRun time problem with VC++ 7 ? Pinmemberatakacs12-Apr-03 10:46 
QuestionRemove MFC dependencies ? PinmemberJerry Evans26-Feb-03 7:51 
AnswerRe: Remove MFC dependencies ? Pinmembermoliate27-Feb-03 10:17 
GeneralZwQuerySystemInformation PinmemberEugene Polonsky5-Dec-02 12:55 
GeneralRe: ZwQuerySystemInformation Pinmembermoliate9-Dec-02 7:40 
GeneralRe: ZwQuerySystemInformation PinmemberEugene Polonsky9-Dec-02 8:21 
GeneralRe: ZwQuerySystemInformation Pinmembermoliate14-Dec-02 16:50 
Generalretrieveing process version PinmemberBlack ghost2-Oct-02 21:32 
GeneralRe: retrieveing process version Pinmembermoliate3-Oct-02 1:44 
GeneralThe Source has been updated... Pinmembermoliate11-Sep-02 4:36 
GeneralHandle leak with fix PinmemberPieter Viljoen18-Jun-02 14:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 10 Sep 2002
Article Copyright 2001 by moliate
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid