How To Enumerate Applications Using Win32 APIs

How To Enumerate Applications Using Win32 APIs

Article ID:175030
Last Review:September 27, 2004
Revision:1.0
This article was previously published under Q175030

SUMMARY

One common programming task is to enumerate all running "applications." The Windows Task Manager is a good 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" on the system. This article provides details on how to perform both these tasks.

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, use the EnumWindows() function. Do not use GetWindow() to create your list of windows, because 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 desktop (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 windows' z-order, and so forth. 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 because the API functions for doing this differ depending on which Win32 operating system is being used. Under Windows 95, Windows 98, Windows Millennium Edition, Windows 2000, and Windows XP, you can use functions from the ToolHelp32 library of APIs. However, under Windows NT, you must use functions from the PSAPI library of APIs, 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 all Win32 operating systems.

Using the ToolHelp32 Library to Enumerate Processes

First we will go through the ToolHelp32 approach. The ToolHelp32 functions that reside in KERNEL32.dll are standard API functions. Note that these APIs are not available under Windows NT 4.0.

ToolHelp32 offers a variety of functions that allow you to enumerate processes and threads in the system, as well as to get memory and module information. However, only the following three functions are 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 by using the CreateToolhelp32Snapshot() function. This function allows you to choose what type of information is stored in the snapshot. Make sure that you include the TH32CS_SNAPPROCESS flag if you are interested in process information. The CreateToolhelp32Snapshot() function returns a HANDLE, which must be passed 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. This will 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 structure. This can be passed to the OpenProcess() API to get a handle to the process. The executable file and path for the process 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 of the PROCESSENTRY32 structure to sizeof(PROCESSENTRY32) before calling Process32First().

Using the PSAPI Library to Enumerate Processes

The Windows NT approach to creating a list of processes uses the PSAPI functions that reside in PSAPI.dll. This file is distributed with the Platform SDK, which is available for download from the following Microsoft Web site:
Microsoft Platform SDK
http://www.microsoft.com/msdownload/platformsdk/sdkupdate/ (http://support.microsoft.com/?scid=http%3a%2f%2fwww.microsoft.com%2fmsdownload%2fplatformsdk%2fsdkupdate%2f)
The PSAPI.h and PSAPI.lib files that you need are also included in the Platform SDK.

To use the functions in the PSAPI library, add the PSAPI.lib file to your project, and include the PSAPI.h file in any modules that call the PSAPI APIs. Remember to distribute the PSAPI.dll file with any executable that uses it, because it is not distributed with Windows NT 4.0. You can download the redistributable version of PSAPI.dll (without the full Platform SDK) from the following location:
Platform SDK Redistributable: PSAPI for Windows NT
http://www.microsoft.com/downloads/release.asp?releaseid=30337 (http://www.microsoft.com/downloads/release.asp?releaseid=3 0337)
Like the ToolHelp32 functions, the PSAPI library also contains a variety of other useful functions. However, this article only discusses the functions that are relevant to enumerating processes: EnumProcesses(), EnumProcessModules(), GetModuleFileNameEx(), and GetModuleBaseName().

The first step in creating a list of processes is to call EnumProcesses(). 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 that will receive the length of the data returned (cbNeeded). The DWORD array is populated with an array of process IDs for the processes that are currently running. The cbNeeded parameter returns the size of the array that is used. The following calculation tells you how many process IDs were returned: nReturned = cbNeeded / sizeof(DWORD).

NOTE: 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 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, and if cbNeeded equals 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 does not 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 returned module handle (hModule) with the GetModuleFileNameEx() or GetModuleBaseName() APIs to get the full path name, or the simple module name for the process executable. Both functions take a handle to the process, a 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() API, you create a list of processes under Windows NT.

16-Bit Processes

On Windows 95, Windows 98, and Windows Millennium Edition, 16-bit applications are equal citizens as far as ToolHelp32 is concerned. 16-Bit applications have process IDs and so forth, just like Win32 applications. However, this is not the case under Windows NT, Windows 2000, or Windows XP. On these operating systems, 16-bit applications run in what is called a Virtual Dos Machine (VDM).

To enumerate 16-bit applications under Windows NT, Windows 2000, and Windows XP, you must use a function called VDMEnumTaskWOWEx(). You must include VDMDBG.h in your source module, and the VDMDBG.lib file must be linked with your project. These two files are included in the Platform SDK.

The declaration of this function is as follows:
INT WINAPI VDMEnumTaskWOWEx( DWORD dwProcessId, TASKENUMPROCEX fp,
                                LPARAM lparam );
				
where dwProcessId is the identifier of the NTVDM process whose 16-bit tasks you want to enumerate. The fp parameter is a pointer to a callback enumeration function. And the lparam parameter is a user-defined value to be passed to your enumeration function.

Your enumeration 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 for each 16-bit task that is running under the NTVDM process that is passed into VDMEnumTaskWOWEx(). You should return FALSE if you want to continue enumerating and TRUE if you want to end enumeration. Note that this is the opposite of EnumWindows().

Sample Code

The following sample code encapsulates the PSAPI and ToolHelp32 functions into one function called EnumProcs(). This function works similarly 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, declare the callback function 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 file name, and the lParam is the user-defined lParam that is passed into EnumProcs().

The EnumProcs() function uses the ToolHelp32 and PSAPI functions through explicit linking, rather than the more common implicit linking. This technique is used so that the code will be binary-compatible across all Win32 operating systems.
//
// EnumProc.c
//
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <vdmdbg.h>

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

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

BOOL WINAPI EnumProcs(PROCENUMPROC lpProc, LPARAM lParam);

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 with the process filename
// and process ID.
//
// lpProc -- Address of callback routine.
//
// lParam -- A user-defined LPARAM value to be passed to
//           the callback routine.
//
// Callback function definition:
// BOOL CALLBACK Proc(DWORD dw, WORD w, LPCSTR lpstr, LPARAM lParam);
//
BOOL WINAPI EnumProcs(PROCENUMPROC lpProc, LPARAM lParam) {

   OSVERSIONINFO  osver;
   HINSTANCE      hInstLib  = NULL;
   HINSTANCE      hInstLib2 = NULL;
   HANDLE         hSnapShot = NULL;
   LPDWORD        lpdwPIDs  = NULL;
   PROCESSENTRY32 procentry;
   BOOL           bFlag;
   DWORD          dwSize;
   DWORD          dwSize2;
   DWORD          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, DWORD *);
   BOOL (WINAPI *lpfEnumProcessModules)(HANDLE, HMODULE *, DWORD,
         LPDWORD);
   DWORD (WINAPI *lpfGetModuleBaseName)(HANDLE, HMODULE, LPTSTR, DWORD);

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

   // Retrieve the OS version
   osver.dwOSVersionInfoSize = sizeof(osver);
   if (!GetVersionEx(&osver))
      return FALSE;

   // If Windows NT 4.0
   if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT
         && osver.dwMajorVersion == 4) {

      __try {

         // Get the procedure addresses explicitly. We do
         // this so we don't have to worry about modules
         // failing to load under OSes other than Windows NT 4.0
         // because references to PSAPI.DLL can't be resolved.
         hInstLib = LoadLibraryA("PSAPI.DLL");
         if (hInstLib == NULL)
            __leave;

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

         // Get procedure addresses.
         lpfEnumProcesses = (BOOL (WINAPI *)(DWORD *, DWORD, DWORD*))
               GetProcAddress(hInstLib, "EnumProcesses");

         lpfEnumProcessModules = (BOOL (WINAPI *)(HANDLE, HMODULE *,
               DWORD, LPDWORD)) GetProcAddress(hInstLib,
               "EnumProcessModules");

         lpfGetModuleBaseName = (DWORD (WINAPI *)(HANDLE, HMODULE,
               LPTSTR, DWORD)) GetProcAddress(hInstLib,
               "GetModuleBaseNameA");

         lpfVDMEnumTaskWOWEx = (INT (WINAPI *)(DWORD, TASKENUMPROCEX,
               LPARAM)) GetProcAddress(hInstLib2, "VDMEnumTaskWOWEx");

         if (lpfEnumProcesses == NULL
               || lpfEnumProcessModules == NULL
               || lpfGetModuleBaseName == NULL
               || lpfVDMEnumTaskWOWEx == NULL)
            __leave;

         //
         // 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);
         do {

            if (lpdwPIDs) {
               HeapFree(GetProcessHeap(), 0, lpdwPIDs);
               dwSize2 *= 2;
            }

            lpdwPIDs = (LPDWORD) HeapAlloc(GetProcessHeap(), 0,
                  dwSize2);
            if (lpdwPIDs == NULL)
               __leave;

            if (!lpfEnumProcesses(lpdwPIDs, dwSize2, &dwSize))
               __leave;

         } 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 to be opened).
            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 will be the
               // EXE module for which we will retrieve the name.
               if (lpfEnumProcessModules(hProcess, &hMod,
                     sizeof(hMod), &dwSize2)) {

                  // Get the module name
                  if (!lpfGetModuleBaseName(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, "NTVDM.EXE") == 0) {

               // Fill in some info for the 16-bit enum proc.
               sInfo.dwPID = lpdwPIDs[dwIndex];
               sInfo.lpProc = lpProc;
               sInfo.lParam = (DWORD) 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;
            }
         }

      } __finally {

         if (hInstLib)
            FreeLibrary(hInstLib);

         if (hInstLib2)
            FreeLibrary(hInstLib2);

         if (lpdwPIDs)
            HeapFree(GetProcessHeap(), 0, lpdwPIDs);
      }

   // If any OS other than Windows NT 4.0.
   } else if (osver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS
         || (osver.dwPlatformId == VER_PLATFORM_WIN32_NT
         && osver.dwMajorVersion > 4)) {

      __try {

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

         // If NT-based OS, load VDMDBG.DLL.
         if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) {
            hInstLib2 = LoadLibraryA("VDMDBG.DLL");
            if (hInstLib2 == NULL)
               __leave;
         }

         // Get procedure addresses. We are linking to
         // these functions explicitly, because a module using
         // this code would fail to load under Windows NT,
         // which does not have the Toolhelp32
         // functions in KERNEL32.DLL.
         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)
            __leave;

         if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) {
            lpfVDMEnumTaskWOWEx = (INT (WINAPI *)(DWORD, TASKENUMPROCEX,
                  LPARAM)) GetProcAddress(hInstLib2, "VDMEnumTaskWOWEx");
            if (lpfVDMEnumTaskWOWEx == NULL)
               __leave;
         }

         // Get a handle to a Toolhelp snapshot of all 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)) {

               // Did we just bump into an NTVDM?
               if (_stricmp(procentry.szExeFile, "NTVDM.EXE") == 0) {

                  // Fill in some info for the 16-bit enum proc.
                  sInfo.dwPID = procentry.th32ProcessID;
                  sInfo.lpProc = lpProc;
                  sInfo.lParam = (DWORD) lParam;
                  sInfo.bEnd = FALSE;

                  // Enum the 16-bit stuff.
                  lpfVDMEnumTaskWOWEx(procentry.th32ProcessID,
                     (TASKENUMPROCEX) Enum16, (LPARAM) &sInfo);

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

               procentry.dwSize = sizeof(PROCESSENTRY32);
               bFlag = lpfProcess32Next(hSnapShot, &procentry);

            } else
               bFlag = FALSE;
         }

      } __finally {

         if (hInstLib)
            FreeLibrary(hInstLib);

         if (hInstLib2)
            FreeLibrary(hInstLib2);
      }

   } 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;
}


BOOL CALLBACK MyProcessEnumerator(DWORD dwPID, WORD wTask,
      LPCSTR szProcess, LPARAM lParam) {

   if (wTask == 0)
      printf("%5u   %s/n", dwPID, szProcess);
   else
      printf("  %5u %s/n", wTask, szProcess);

   return TRUE;
}


void main() {
   EnumProcs((PROCENUMPROC) MyProcessEnumerator, 0);
}
				

REFERENCES

Pietrek, Matt, "Under the Hood," Microsoft Systems Journal, no. 8 (August 1996).

Pietrek, Matt, "Under the Hood," Microsoft Systems Journal, no. 11 (November 1996).

APPLIES TO
Microsoft Win32 Application Programming Interface (API), when used with:
  Microsoft Windows 95 Service Pack 1
  Microsoft Windows 98 Standard Edition
  Microsoft Windows Millennium Edition
  Microsoft Windows NT 4.0 Service Pack 7
  Microsoft Windows 2000 Standard Edition
  Microsoft Windows XP Standard Edition
Keywords: 
kbhowto kbthread kbkernbase KB175030
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值