HOWTO: Enumerate Applications in Win32

Last reviewed: January 6, 1998
Article ID: Q175030
The information in this article applies to:
  • Microsoft Win32 Application Programming Interface (API) included with: - Microsoft Windows NT 4.0 - Microsoft Windows 95

SUMMARY

One common programming problem in Win32 is the need to enumerate all "applications". The Task Manager under Windows NT 4.0 is a perfect example of this. It lists "applications" in two ways. The first tab of the Task Manager lists all "application windows" on the desktop. The second tab of the Task Manager lists all the "processes" in the system. This article provides details on how to perform both these tasks in Windows 95 and Windows NT.

MORE INFORMATION

Enumerating Top-Level Windows

If you compare enumerating processes and enumerating top-level windows on the desktop, enumerating top-level windows is probably easier. To enumerate top-level windows under both Windows NT and Windows 95 platforms, use the EnumWindows() function. Do not use GetWindow() to create your list of windows, as it can be confused by z-order changes and lost windows.

EnumWindows() takes a pointer to a callback function and a user-defined LPARAM value as its parameters. It calls the callback function once per window on the screen (or top-level window). The callback function can then do some processing with this window handle, such as add it to a list. This method is guaranteed not to be confused by changes in window's z-order, etc. Once you have a window handle, you can get its title by calling GetWindowText().

Enumerating Processes

Creating a list of processes in the system is a little more complex than enumerating windows. This is primarily due to the fact that the API functions for doing this are completely different under Windows 95 and Windows NT. Under Windows 95, you must use functions from the ToolHelp32 group of APIs. Under Windows NT, you use functions from PSAPI.DLL, which is available in the Platform SDK. This article will discuss both of these techniques and also provide a sample wrapper function called "EnumProcs()" that works under Windows NT and Windows 95.

Windows 95 and ToolHelp32:

First let's go through the ToolHelp32 approach under Windows 95. The ToolHelp32 functions that reside in the KERNEL32.DLL are standard API functions available only under Windows 95. ToolHlp32 offers a variety of functions that allow you to enumerate processes and threads in the system, as well as get memory and module information. However, the following three functions are only needed when enumerating processes:

   CreateToolhelp32Snapshot(), Process32First(), and Process32Next().

The first step in using the ToolHelp32 functions is to create a "snapshot" of the information in the system. You do this using the CreateToolhelp32Snapshot() function. This function allows you to choose what type of information is stored in the snapshot. Make sure you include the TH32CS_SNAPPROCESS flag if you are interested in process information. This function returns a HANDLE, and it is important to remember to pass the handle to CloseHandle() after you are finished with it.

Next, to retrieve a list of processes from the snapshot you will call Process32First once, followed by repeated calls to Process32Next. Do this until one of these functions returns FALSE to iterate through the list of processes in the snapshot. Both of these functions take the handle to the snapshot, and a pointer to a PROCESSENTRY32 structure as their parameters.

After a call to Process32First or Process32Next, the PROCESSENTRY32 structure will contain useful information about one of the processes in the system. The process ID is in the th32ProcessID member of the struct. This can be passed to the OpenProcess() API to get a handle to the process. The process' executable file and path are stored in the szExeFile member of the structure. Other useful information is also available in this structure.

NOTE: It is important to remember to set the dwSize member to sizeof(PROCESSENTRY32) before calling Process32First().

Windows NT and the PSAPI.DLL:

The Windows NT approach to creating a list of processes uses functions from the PSAPI.DLL. The PSAPI.DLL file is distributed with the Platform SDK, which is available at http://www.microsoft.com/msdn/sdk. The PSAPI.H and PSAPI.LIB files that you need are also included with the Platform SDK.

To use the functions in the PSAPI.DLL, add the PSAPI.LIB file to your project, and include the PSAPI.H file with any modules that call the PSAPI.DLL functions. Remember to distribute the PSAPI.DLL file with any executable that uses it, as it is not currently distributed with the operating system.

Like the ToolHelp32 functions, the PSAPI.DLL also contains a variety of useful functions. However, this article only discusses those functions that are relevant to enumerating processes:

   EnumProcesses(), EnumProcessModules(), GetModuleFileNameEx(),
   GetModuleBaseName().

EnumProcesses() is the first step in creating a list of processes in the system. Following is its declaration:

   BOOL EnumProcesses( DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded );

EnumProcesses() takes a pointer to a DWORD array (lpidProcess), the size of the array (cb), and a pointer to a DWORD to return the length of the data returned(cbNeeded). The DWORD array is populated with an array of process ID's for the processes in the system. The pointer to the DWORD parameter (cbNeeded) returns the size of the array that is used. The following calculation tells you how many process IDs were returned: nReturned = cbNeeded / sizeof(DWORD). One important note should be made. Although the documentation names the returned DWORD "cbNeeded", there is actually no way to find out how big the passed in array must be. EnumProcesses() will never return a value in cbNeeded that is larger than the size of array value that you passed in the cb parameter. As a result the only way to assure success with the EnumProcesses() function is to allocate a DWORD array, if cbNeeded == cb upon return, allocate a larger array and try again until cbNeeded is smaller than cb.

Now you have an array with each process ID in the system. If your goal is to get the name of the process, then you must first get a handle. To get a handle from a process ID, use OpenProcess().

Once you have a handle you will need to get the "first" module of the process. To get the first module of a process call the EnumProcessModules() API with the following parameters:

   EnumProcessModules( hProcess, &hModule, sizeof(hModule), &cbReturned );

This will put the handle of the first module of the process in the hModule variable. Remember that a process doesn't really have a name, but that the first module in the process is going to be the executable of the process. Now you can use the hModule with the GetModuleFileNameEx(), GetModuleBaseName() to get the full path name, or the simple module name for the process executable. Both functions take the handle to the process, handle to the module, and a buffer pointer in which to return the name, followed by the size of the buffer.

By repeating this for each process ID returned with the EnumProcesses(),you create a list of processes under Windows NT.

16-Bit Processes:

In Windows 95 16-bit applications are equal citizens as far as ToolHelp32 is concerned. 16-Bit applications have process IDs etc., just like Win32 applications. However, this is not the case under Windows NT.

16-Bit applications running under Windows NT run in what is called a Virtual Dos Machine (VDM). EnumProcesses will not recognize any 16-bit applications in the system, however it will return the 32-bit NTVDM processes under which the 16-bit EXE's are running. To enumerate 16-bit applications under Windows NT you must use a function called VDMEnumTaskWOWEx(). You must include the VDMDBG.H in your source module, and the VDMDBG.LIB file should be linked with your project. These two files are shipped with the Platform SDK.

The declaration of this function is as follows:

   INT WINAPI VDMEnumTaskWOWEx( DWORD dwProcessId, TASKENUMPROCEX fp,
                                LPARAM lparam );

where dwProcessId is the id of the NTVDM process whose 16-bit tasks you want to enumerate. The fp parameter is a pointer to a callback "enum" function. And the lparam parameter is a user-defined lparam to be passed to your enum function.

Your "enum" function should be defined as follows:

   BOOL WINAPI Enum16( DWORD dwThreadId, WORD hMod16, WORD hTask16, PSZ
                       pszModName, PSZ pszFileName, LPARAM lpUserDefined );

This function will be called once per 16-bit task running under the NTVDM process passed into VDMEnumTaskWOWEx(). You should return FALSE if you wish to continue enumerating and TRUE if you wish to end enumeration. NOTE: This is the opposite of EnumWindows().

Sample Code

The following sample code encapsulates the PSAPI.DLL and ToolHelp32 functions into one function called "EnumProcs()". This function works similar to EnumWindows() in that it takes a pointer to a function and calls it repeatedly, once for each process in the system. Following is the declaration for the function:

   BOOL WINAPI EnumProcs( PROCENUMPROC lpProc, LPARAM lParam );

If you use this function, the callback function should be declared as follows:

   BOOL CALLBACK Proc( DWORD dw, WORD w16, LPCSTR lpstr, LPARAM lParam );

The dw parameter will contain the ID, the w16 is the 16-bit task number or 0 if 32-bit process (always zero under Windows 95), the lpstr parameter will point to the filename, and the lParam is the user-defined lParam passed into EnumProcs().

The EnumProcs() function uses the ToolHelp32 and PSAPI.DLL functions via explicit linking, rather than the more common implicit linking. This technique is used so that the code including the EnumProcs() function will be binary compatible across both Windows NT and Windows 95. (For example, implicit linking of a ToolHelp32 function would cause an EXE to fail to load and run under Windows NT.)

   /*********************
   EnumProc.h
   *********************/
   #include <windows.h>

   typedef BOOL (CALLBACK *PROCENUMPROC)( DWORD, WORD, LPSTR,
      LPARAM ) ;

   BOOL WINAPI EnumProcs( PROCENUMPROC lpProc, LPARAM lParam ) ;

   /*********************
   EnumProc.c (or .cpp)
   *********************/
   #include "EnumProc.h"
   #include <tlhelp32.h>
   #include <vdmdbg.h>

   typedef struct
   {
      DWORD          dwPID ;
      PROCENUMPROC   lpProc ;
      DWORD          lParam ;
      BOOL           bEnd ;
   } EnumInfoStruct ;

   BOOL WINAPI Enum16( DWORD dwThreadId, WORD hMod16, WORD hTask16,
      PSZ pszModName, PSZ pszFileName, LPARAM lpUserDefined ) ;

   // The EnumProcs function takes a pointer to a callback function
   // that will be called once per process in the system providing
   // process EXE filename and process ID.
   // Callback function definition:
   // BOOL CALLBACK Proc( DWORD dw, LPCSTR lpstr, LPARAM lParam ) ;
   //
   // lpProc -- Address of callback routine.
   //
   // lParam -- A user-defined LPARAM value to be passed to
   //           the callback routine.
   BOOL WINAPI EnumProcs( PROCENUMPROC lpProc, LPARAM lParam )
   {
      OSVERSIONINFO  osver ;
      HINSTANCE      hInstLib ;
      HINSTANCE      hInstLib2 ;
      HANDLE         hSnapShot ;
      PROCESSENTRY32 procentry ;
      BOOL           bFlag ;
      LPDWORD        lpdwPIDs ;
      DWORD          dwSize, dwSize2, dwIndex ;
      HMODULE        hMod ;
      HANDLE         hProcess ;
      char           szFileName[ MAX_PATH ] ;
      EnumInfoStruct sInfo ;

      // ToolHelp Function Pointers.
      HANDLE (WINAPI *lpfCreateToolhelp32Snapshot)(DWORD,DWORD) ;
      BOOL (WINAPI *lpfProcess32First)(HANDLE,LPPROCESSENTRY32) ;
      BOOL (WINAPI *lpfProcess32Next)(HANDLE,LPPROCESSENTRY32) ;

      // PSAPI Function Pointers.
      BOOL (WINAPI *lpfEnumProcesses)( DWORD *, DWORD cb, DWORD * );
      BOOL (WINAPI *lpfEnumProcessModules)( HANDLE, HMODULE *,
         DWORD, LPDWORD );
      DWORD (WINAPI *lpfGetModuleFileNameEx)( HANDLE, HMODULE,
         LPTSTR, DWORD );

      // VDMDBG Function Pointers.
      INT (WINAPI *lpfVDMEnumTaskWOWEx)( DWORD,
         TASKENUMPROCEX  fp, LPARAM );


      // Check to see if were running under Windows95 or
      // Windows NT.
      osver.dwOSVersionInfoSize = sizeof( osver ) ;
      if( !GetVersionEx( &osver ) )
      {
         return FALSE ;
      }

      // If Windows NT:
      if( osver.dwPlatformId == VER_PLATFORM_WIN32_NT )
      {

         // Load library and get the procedures explicitly. We do
         // this so that we don't have to worry about modules using
         // this code failing to load under Windows 95, because
         // it can't resolve references to the PSAPI.DLL.
         hInstLib = LoadLibraryA( "PSAPI.DLL" ) ;
         if( hInstLib == NULL )
            return FALSE ;

         hInstLib2 = LoadLibraryA( "VDMDBG.DLL" ) ;
         if( hInstLib2 == NULL )
            return FALSE ;

         // Get procedure addresses.
         lpfEnumProcesses = (BOOL(WINAPI *)(DWORD *,DWORD,DWORD*))
            GetProcAddress( hInstLib, "EnumProcesses" ) ;
         lpfEnumProcessModules = (BOOL(WINAPI *)(HANDLE, HMODULE *,
            DWORD, LPDWORD)) GetProcAddress( hInstLib,
            "EnumProcessModules" ) ;
         lpfGetModuleFileNameEx =(DWORD (WINAPI *)(HANDLE, HMODULE,
            LPTSTR, DWORD )) GetProcAddress( hInstLib,
            "GetModuleFileNameExA" ) ;
         lpfVDMEnumTaskWOWEx =(INT(WINAPI *)( DWORD, TASKENUMPROCEX,
            LPARAM))GetProcAddress( hInstLib2, "VDMEnumTaskWOWEx" );
         if( lpfEnumProcesses == NULL ||
            lpfEnumProcessModules == NULL ||
            lpfGetModuleFileNameEx == NULL ||
            lpfVDMEnumTaskWOWEx == NULL)
            {
               FreeLibrary( hInstLib ) ;
               FreeLibrary( hInstLib2 ) ;
               return FALSE ;
            }

         // Call the PSAPI function EnumProcesses to get all of the
         // ProcID's currently in the system.
         // NOTE: In the documentation, the third parameter of
         // EnumProcesses is named cbNeeded, which implies that you
         // can call the function once to find out how much space to
         // allocate for a buffer and again to fill the buffer.
         // This is not the case. The cbNeeded parameter returns
         // the number of PIDs returned, so if your buffer size is
         // zero cbNeeded returns zero.
         // NOTE: The "HeapAlloc" loop here ensures that we
         // actually allocate a buffer large enough for all the
         // PIDs in the system.
         dwSize2 = 256 * sizeof( DWORD ) ;
         lpdwPIDs = NULL ;
         do
         {
            if( lpdwPIDs )
            {
               HeapFree( GetProcessHeap(), 0, lpdwPIDs ) ;
               dwSize2 *= 2 ;
            }
            lpdwPIDs = HeapAlloc( GetProcessHeap(), 0, dwSize2 );
            if( lpdwPIDs == NULL )
            {
               FreeLibrary( hInstLib ) ;
               FreeLibrary( hInstLib2 ) ;
               return FALSE ;
            }
            if( !lpfEnumProcesses( lpdwPIDs, dwSize2, &dwSize ) )
            {
               HeapFree( GetProcessHeap(), 0, lpdwPIDs ) ;
               FreeLibrary( hInstLib ) ;
               FreeLibrary( hInstLib2 ) ;
               return FALSE ;
            }
         }while( dwSize == dwSize2 ) ;

         // How many ProcID's did we get?
         dwSize /= sizeof( DWORD ) ;

         // Loop through each ProcID.
         for( dwIndex = 0 ; dwIndex < dwSize ; dwIndex++ )
         {
            szFileName[0] = 0 ;
            // Open the process (if we can... security does not
            // permit every process in the system).
            hProcess = OpenProcess(
               PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
               FALSE, lpdwPIDs[ dwIndex ] ) ;
            if( hProcess != NULL )
            {
               // Here we call EnumProcessModules to get only the
               // first module in the process this is important,
               // because this will be the .EXE module for which we
               // will retrieve the full path name in a second.
               if( lpfEnumProcessModules( hProcess, &hMod,
                  sizeof( hMod ), &dwSize2 ) )
               {
                  // Get Full pathname:
                  if( !lpfGetModuleFileNameEx( hProcess, hMod,
                     szFileName, sizeof( szFileName ) ) )
                  {
                     szFileName[0] = 0 ;
                    }
               }
               CloseHandle( hProcess ) ;
            }
            // Regardless of OpenProcess success or failure, we
            // still call the enum func with the ProcID.
            if(!lpProc( lpdwPIDs[dwIndex], 0, szFileName, lParam))
               break ;

            // Did we just bump into an NTVDM?
            if( _stricmp( szFileName+(strlen(szFileName)-9),
               "NTVDM.EXE")==0)
            {
               // Fill in some info for the 16-bit enum proc.
               sInfo.dwPID = lpdwPIDs[dwIndex] ;
               sInfo.lpProc = lpProc ;
               sInfo.lParam = lParam ;
               sInfo.bEnd = FALSE ;
               // Enum the 16-bit stuff.
               lpfVDMEnumTaskWOWEx( lpdwPIDs[dwIndex],
                  (TASKENUMPROCEX) Enum16,
                  (LPARAM) &sInfo);

               // Did our main enum func say quit?
               if(sInfo.bEnd)
                  break ;
            }
         }

         HeapFree( GetProcessHeap(), 0, lpdwPIDs ) ;
         FreeLibrary( hInstLib2 ) ;

      // If Windows 95:
      }else if( osver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS )
      {


         hInstLib = LoadLibraryA( "Kernel32.DLL" ) ;
         if( hInstLib == NULL )
            return FALSE ;

         // Get procedure addresses.
         // We are linking to these functions of Kernel32
         // explicitly, because otherwise a module using
         // this code would fail to load under Windows NT,
         // which does not have the Toolhelp32
         // functions in the Kernel 32.
         lpfCreateToolhelp32Snapshot=
            (HANDLE(WINAPI *)(DWORD,DWORD))
            GetProcAddress( hInstLib,
            "CreateToolhelp32Snapshot" ) ;
         lpfProcess32First=
            (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))
            GetProcAddress( hInstLib, "Process32First" ) ;
         lpfProcess32Next=
            (BOOL(WINAPI *)(HANDLE,LPPROCESSENTRY32))
            GetProcAddress( hInstLib, "Process32Next" ) ;
         if( lpfProcess32Next == NULL ||
            lpfProcess32First == NULL ||
            lpfCreateToolhelp32Snapshot == NULL )
         {
            FreeLibrary( hInstLib ) ;
            return FALSE ;
         }

         // Get a handle to a Toolhelp snapshot of the systems
         // processes.
         hSnapShot = lpfCreateToolhelp32Snapshot(
            TH32CS_SNAPPROCESS, 0 ) ;
         if( hSnapShot == INVALID_HANDLE_VALUE )
         {
            FreeLibrary( hInstLib ) ;
            return FALSE ;
         }

         // Get the first process' information.
         procentry.dwSize = sizeof(PROCESSENTRY32) ;
         bFlag = lpfProcess32First( hSnapShot, &procentry ) ;

         // While there are processes, keep looping.
         while( bFlag )
         {
            // Call the enum func with the filename and ProcID.
            if(lpProc( procentry.th32ProcessID, 0,
               procentry.szExeFile, lParam ))
            {
               procentry.dwSize = sizeof(PROCESSENTRY32) ;
               bFlag = lpfProcess32Next( hSnapShot, &procentry );
            }else
               bFlag = FALSE ;
         }


      }else
         return FALSE ;

      // Free the library.
      FreeLibrary( hInstLib ) ;

      return TRUE ;
   }

   BOOL WINAPI Enum16( DWORD dwThreadId, WORD hMod16, WORD hTask16,
      PSZ pszModName, PSZ pszFileName, LPARAM lpUserDefined )
   {
      BOOL bRet ;

      EnumInfoStruct *psInfo = (EnumInfoStruct *)lpUserDefined ;

      bRet = psInfo->lpProc( psInfo->dwPID, hTask16, pszFileName,
         psInfo->lParam ) ;

      if(!bRet)
      {
         psInfo->bEnd = TRUE ;
      }

      return !bRet;
   }

REFERENCES

"Microsoft Systems Journal," August 1996 Number 8, "Under the Hood," Matt Pietrek

"Microsoft Systems Journal," November 1996 Number 11, "Under the Hood," Matt Pietrek


Additional query words: processes list listof find finding program running
Keywords : BseDebug BseMisc BseProcThrd
Version : WINNT:4.0
Platform : winnt
Issue type : kbhowto


THE INFORMATION PROVIDED IN THE MICROSOFT KNOWLEDGE BASE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL MICROSOFT CORPORATION OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF MICROSOFT CORPORATION OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE FOREGOING LIMITATION MAY NOT APPLY.

Last reviewed: January 6, 1998
© 1998 Microsoft Corporation. All rights reserved. Terms of Use.