2009년 11월 6일 금요일

Win32 API를 사용하여 응용 프로그램 열거

요약

한 가지 일반적인 프로그래밍 작업으로는 실행 중인 모든 "응용 프로그램"을 열거하는 작업이 있습니다. Windows 작업 관리자는 이러한 좋은 예로, "응용 프로그램"을 두 가지 방식으로 나열합니다. 작업 관리자의 첫 번째 탭은 데스크톱에서 실행 중인 "모든 응용 프로그램 창"을 나열하며 두 번째 탭은 시스템의 모든 "프로세스"를 나열합니다. 이 문서에서는 이러한 두 작업을 수행하는 방법을 자세하게 설명합니다.

 

추가 정보

최상위 창 열거

데스크톱의 프로세스 열거 작업과 최상위 창 열거 작업을 비교해 보면 최상위 창 열거 작업이 좀더 간단합니다. 최상위 창을 열거하려면 EnumWindows() 함수를 사용하십시오. z축 변경 및 손실 창으로 인해 혼동될 수 있으므로 GetWindow()를 사용하여 창 목록을 만들지 마십시오.

EnumWindows()는 콜백 함수에 대한 포인터와 사용자 정의 LPARAM 값을 매개 변수로 사용하며, 데스크톱에서 실행 중인 각 창(또는 최상위 창)에 대해 콜백 함수를 호출합니다. 그러면 콜백 함수는 해당 창 핸들을 사용하여 창을 목록에 추가하는 등의 작업을 처리할 수 있습니다. 이 방법을 사용하면 창의 z축 변경 등으로 인해 혼동되지 않습니다. 창 핸들을 얻으면 GetWindowText()를 호출하여 제목을 가져올 수 있습니다.

 

프로세스 열거

시스템의 프로세스 목록을 만드는 작업은 창 열거 작업보다 약간 복잡합니다. 주로 그 이유는 사용하는 Win32 운영 체제에 따라 이 작업을 수행하는 API 함수가 달라지기 때문입니다. Windows 95, Windows 98, Windows Millennium Edition, Windows 2000 및 Windows XP에서는 API의 ToolHelp32 라이브러리에 있는 함수를 사용할 수 있지만, Windows NT에서는 Platform SDK에서 제공하는 API의 PSAPI 라이브러리에 있는 함수를 사용해야 합니다. 이 문서에서는 이러한 두 가지 방법을 모두 설명하며 모든 Win32 운영 체제에서 작동하는 EnumProcs()라는 예제 래퍼 함수도 제공합니다.

 

ToolHelp32 라이브러리를 사용하여 프로세스 열거

먼저 ToolHelp32 방법을 살펴봅니다. KERNEL32.dll에 있는 ToolHelp32 함수는 표준 API 함수입니다. 이러한 API는 Windows NT 4.0에서 사용할 수 없습니다.

ToolHelp32는 시스템의 프로세스와 스레드를 열거할 뿐 아니라 메모리와 모듈 정보를 얻을 수 있는 다양한 함수를 제공합니다. 그러나 프로세스를 열거할 때는 CreateToolhelp32Snapshot(), Process32First(), Process32Next() 등의 세 함수만 필요합니다.

ToolHelp32 함수를 사용하는 첫 번째 단계는 시스템 정보에 대한 "스냅샷"을 만드는 것입니다. 스냅샷은 CreateToolhelp32Snapshot() 함수를 사용하여 만듭니다. 이 함수를 사용하면 스냅샷에 저장되는 정보의 종류를 선택할 수 있습니다. 프로세스 정보를 저장하려면 TH32CS_SNAPPROCESS 플래그를 포함시켜야 합니다. CreateToolhelp32Snapshot() 함수는 완료되면, CloseHandle()에 전달되어야 하는 핸들을 반환합니다.

스냅샷을 만든 후 이 스냅샷에서 프로세스 목록을 검색하려면 Process32First를 한 번 호출한 다음 Process32Next를 반복 호출합니다. 이러한 함수 중 하나가 FALSE를 반환할 때까지 이 작업을 계속합니다. 이 작업은 스냅샷에 있는 프로세스 목록 전체에 반복됩니다. 이러한 두 함수는 모두 스냅샷에 대한 핸들과 PROCESSENTRY32 구조에 대한 포인터를 매개 변수로 사용합니다.

Process32First 또는 Process32Next를 호출하면 PROCESSENTRY32 구조에는 시스템 프로세스 중 하나에 대한 유용한 정보가 포함됩니다. 프로세스 ID는 PROCESSENTRY32 구조의 th32ProcessID 구성원에 있습니다. 이것을 OpenProcess() API에 전달하여 프로세스에 대한 핸들을 얻을 수 있습니다. 실행 파일 및 프로세스 경로는 PROCESSENTRY32 구조의 szExeFile 구성원에 저장됩니다. 이 구조에는 다른 유용한 정보도 들어 있습니다.

참고:Process32First()를 호출하기 전에 PROCESSENTRY32 구조의 dwSize 구성원을 sizeof(PROCESSENTRY32)로 설정하는 것이 중요합니다.

 

PSAPI 라이브러리를 사용하여 프로세스 열거

Windows NT에서 프로세스 목록을 만들려면 PSAPI.dll에 있는 PSAPI 함수를 사용해야 합니다. 이 파일은 다음 Microsoft 웹 사이트에서 다운로드할 수 있는 플랫폼 Platform SDK와 함께 배포됩니다.
Microsoft Platform SDK
http://www.microsoft.com/msdownload/platformsdk/sdkupdate/ (http://www.microsoft.com/msdownload/platformsdk/sdkupdate/)
Platform SDK에는 필요한 PSAPI.h와 PSAPI.lib 파일도 포함되어 있습니다.

PSAPI 라이브러리에 있는 함수를 사용하려면 PSAPI.lib 파일을 프로젝트에 추가하고 PSAPI API를 호출하는 모듈에 PSAPI.h 파일을 포함합니다. Windows NT 4.0에는 PSAPI.dll 파일을 사용하는 실행 파일이 배포되지 않으므로 반드시 PSAPI.dll 파일과 함께 실행 파일을 배포하십시오. 재배포 가능한 버전의 PSAPI.dll(전체 Platform SDK 제외)은 다음 위치에서 다운로드할 수 있습니다.
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)
ToolHelp32 함수와 마찬가지로 PSAPI 라이브러리에도 기타 유용한 함수가 많이 포함되어 있습니다. 그러나 이 문서에서는 EnumProcesses(), EnumProcessModules(), GetModuleFileNameEx(), GetModuleBaseName() 등 프로세스 열거 관련 함수만 설명합니다.

프로세스 목록을 만드는 첫 번째 단계는 EnumProcesses()를 호출하는 것입니다. 다음과 같이 선언합니다.
BOOL EnumProcesses( DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded );
				
EnumProcesses()는 DWORD 배열에 대한 포인터(lpidProcess), 배열의 크기(cb) 및 반환된 데이터의 길이를 받는 DWORD에 대한 포인터(cbNeeded)를 매개 변수로 사용합니다. DWORD 배열은 현재 실행 중인 프로세스에 대한 프로세스 ID의 배열로 채워집니다. cbNeeded 매개 변수는 사용되는 배열의 크기를 반환합니다. nReturned = cbNeeded / sizeof(DWORD)는 얼마나 많은 프로세스 ID가 반환되었는지 계산합니다.

참고: 이 문서에서는 반환된 DWORD "cbNeeded"라고 지정하지만 실제로 어느 정도 크기의 배열이 전달되어야 하는지 확인할 방법은 없습니다. EnumProcesses()cb 매개 변수에 전달된 배열 값보다 큰 cbNeeded 값을 반환하지 않습니다. 따라서 EnumProcesses() 함수를 성공적으로 사용하는 유일한 방법은 DWORD 배열을 할당하고, 반환 시 cbNeededcb와 같으면 더 큰 배열을 할당하여 cbNeededcb보다 작아질 때까지 다시 시도하는 것입니다.

이제 시스템의 각 프로세스 ID가 포함된 배열이 만들어집니다. 프로세스 이름을 아는 것이 목적이라면 먼저 핸들을 얻어야 합니다. 프로세스 ID에서 핸들을 얻으려면 OpenProcess()를 사용하십시오.

핸들을 얻은 후에는 프로세스의 첫 번째 모듈을 얻어야 합니다. 프로세스의 첫 번째 모듈을 얻으려면 다음 매개 변수를 사용하여 EnumProcessModules() API를 호출하십시오.
EnumProcessModules( hProcess, &hModule, sizeof(hModule), &cbReturned );
				
이 함수는 hModule 변수에 프로세스의 첫 번째 모듈에 대한 핸들을 저장합니다. 실제로 프로세스에는 이름이 없지만 프로세스의 첫 번째 모듈이 프로세스의 실행 파일이 됩니다. 이제 반환된 모듈 핸들(hModule)을 GetModuleFileNameEx() 또는 GetModuleBaseName() API에서 사용하여 전체 파일 경로 이름이나 프로세스 실행 파일의 간단한 모듈 이름을 얻을 수 있습니다. 두 함수 모두 프로세스에 대한 핸들, 모듈에 대한 핸들, 이름을 반환하는 버퍼 포인터와 버퍼 크기를 매개 변수로 사용합니다.

EnumProcesses() API를 통해 반환된 각 프로세스 ID에 대해 이 단계를 반복하면 Windows NT의 프로세스 목록이 만들어집니다.

 

16비트 프로세스

Windows 95, Windows 98 및 Windows Millennium Edition에서는 16비트 응용 프로그램을 Win32 응용 프로그램에서처럼 ToolHelp32를 사용하여 열거할 수 있습니다. 16비트 응용 프로그램에도 Win32 응용 프로그램과 마찬가지로 프로세스 ID 등이 있습니다. 그러나 Windows NT, Windows 2000 또는 Windows XP 운영 체제에서는 경우가 다릅니다. 이러한 운영 체제에서는 16비트 응용 프로그램이 VDM(Virtual Dos Machine)에서 실행됩니다.

따라서 Windows NT, Windows 2000 및 Windows XP에서 16비트 응용 프로그램을 열거하려면 VDMEnumTaskWOWEx()라는 함수를 사용해야 합니다. 원본 모듈에 VDMDBG.h를 포함해야 하며 VDMDBG.lib 파일을 사용자 프로젝트와 연결해야 합니다. 이 두 파일은 Platform SDK에 포함되어 있습니다.

이 함수의 선언은 다음과 같습니다.
INT WINAPI VDMEnumTaskWOWEx( DWORD dwProcessId, TASKENUMPROCEX fp,
                                LPARAM lparam );
				
여기서 dwProcessId는 열거할 16비트 작업의 NTVDM 프로세스에 대한 식별자입니다. fp 매개 변수는 콜백 열거 함수에 대한 포인터이며, lparam 매개 변수는 열거 함수에 전달될 사용자 정의 값입니다.

열거 함수는 다음과 같이 정의해야 합니다.
BOOL WINAPI Enum16( DWORD dwThreadId, WORD hMod16, WORD hTask16, PSZ
                       pszModName, PSZ pszFileName, LPARAM lpUserDefined );
				
이 함수는 VDMEnumTaskWOWEx()에 전달되며 NTVDM 프로세스에서 실행 중인 각 16비트 작업에 대해 한 번 호출됩니다. 열거를 계속하려면 FALSE를, 열거를 끝내려면 TRUE를 반환해야 합니다. 이것은 EnumWindows()와 반대입니다.

 

예제 코드

다음 예제 코드는 PSAPI 및 ToolHelp32 함수를 EnumProcs()라는 한 함수로 캡슐화합니다. 이 함수는 함수에 대한 포인터를 사용하고 이것을 반복적으로 호출한다는 점에서 EnumWindows()와 비슷하며, 시스템에 있는 각 프로세스에 대해 한 번 작동합니다. 이 함수의 선언은 다음과 같습니다.
BOOL WINAPI EnumProcs( PROCENUMPROC lpProc, LPARAM lParam );
				
이 함수를 사용하는 경우 콜백 함수는 다음과 같이 선언하십시오.
BOOL CALLBACK Proc( DWORD dw, WORD w16, LPCSTR lpstr, LPARAM lParam );
				
dw 매개 변수는 ID를 포함하게 되고, 32비트 프로세스인 경우 "w16"은 16비트 작업 번호나 0(Windows 95에서는 항상 0)이고, lpstr 매개 변수는 해당 파일 이름을 가리키며, lParamEnumProcs()에 전달되는 사용자 정의 lParam입니다.

EnumProcs() 함수는 보다 일반적인 암시적 연결 대신 명시적 연결을 통해 ToolHelp32 및 PSAPI 함수를 사용합니다. 이 방법을 사용하면 모든 Win32 운영 체제에서 코드가 이진 호환됩니다.
// 
// 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);
}

댓글 없음:

댓글 쓰기