Сообщений 12 Оценка 512 Оценить |
Тестовое приложение Process Viewer
Win32 предоставляет несколько способов перечисления запущенных процессов. К сожалению, нет единого способа, который бы работал на всех Win32-платформах. Программистам приходится комбинировать несколько методов в одной программе, чтобы она работала на всех версиях Windows.
Мы рассмотрим следующие методы:
Каждый метод мы будем иллюстрировать с помощью функции, имеющей следующий прототип:
// эта функция вызывается для каждого процесса в перечислении typedef BOOL (CALLBACK * PFNENUMPROC)( IN DWORD dwProcessId, // идентификатор процесса IN LPCTSTR pszProcessName, // имя процесса; NULL, если недоступно IN LPARAM lParam // пользовательский параметр ); // функция перечисления процессов BOOL WINAPI EnumProcesses_Method( IN LPCTSTR pszMachineName, // имя компьютера IN PFNENUMPROC pfnEnumProc, // пользовательская функция IN LPARAM lParam // пользовательский параметр ); |
Функция перечисления вызывает для каждого процесса указанную пользовательскую функцию, передавая ей информацию об очередном процессе. Пользователькая функция распоряжается этими данными в соответствии с логикой программы, например, в тестовом приложении для этой статьи, она добавляет очередной элемент в список. Некоторые из рассматриваемых нами методов позволяют перечислить процессы на других компьютеров, поэтому прототип функции содержит параметр pszMachineName, позволяющий задать имя компьютера. Значение NULL для этого параметра означает перечисление процессов на локальном компьютере.
Библиотека Process Status Helper, известная также под названием PSAPI, предоставляет набор функций, позволяющих получить информацию о процессах и драйверах устройств. Библиотека поставляется в составе Windows 2000/XP и доступна в качестве устанавливаемой компоненты для Windows NT 4.0.
Для перечисления процессов библиотека предоставляет функцию EnumProcesses, которая возвращает массив идентификаторов запущенных процессов. Ниже приведен текст функции EnumProcesses_PsApi, реализующей перечисление процессов с помощью PSAPI.
#include <psapi.h> BOOL WINAPI EnumProcesses_PsApi( IN LPCTSTR pszMachineName, IN PFNENUMPROC pfnEnumProc, IN LPARAM lParam ) { _ASSERTE(pfnEnumProc != NULL); if (pszMachineName != NULL) return SetLastError(ERROR_INVALID_PARAMETER), FALSE; HINSTANCE hPsApi; BOOL (WINAPI * _EnumProcesses)(DWORD *, DWORD, DWORD *); BOOL (WINAPI * _EnumProcessModules)(HANDLE, HMODULE *, DWORD, DWORD *); BOOL (WINAPI * _GetModuleFileNameEx)(HANDLE, HMODULE, LPTSTR, DWORD); // загружаем PSAPI.DLL hPsApi = LoadLibrary(_T("psapi.dll")); if (hPsApi == NULL) return FALSE; // находим нужные функции в PSAPI.DLL *(FARPROC *)&_EnumProcesses = GetProcAddress(hPsApi, "EnumProcesses"); *(FARPROC *)&_EnumProcessModules = GetProcAddress(hPsApi, "EnumProcessModules"); #ifdef UNICODE *(FARPROC *)&_GetModuleFileNameEx = GetProcAddress(hPsApi, "GetModuleFileNameExW"); #else *(FARPROC *)&_GetModuleFileNameEx = GetProcAddress(hPsApi, "GetModuleFileNameExA"); #endif if (_EnumProcesses == NULL || _EnumProcessModules == NULL || _GetModuleFileNameEx == NULL) { FreeLibrary(hPsApi); return SetLastError(ERROR_PROC_NOT_FOUND), FALSE; } // получаем описатель кучи процесса по умолчанию HANDLE hHeap = GetProcessHeap(); DWORD dwError; DWORD cbReturned; DWORD cbAlloc = 128; DWORD * pdwIds = NULL; OSVERSIONINFO osvi; osvi.dwOSVersionInfoSize = sizeof(osvi); GetVersionEx(&osvi); DWORD dwSystemId = (osvi.dwMajorVersion < 5) ? 2 : 8; do { cbAlloc *= 2; if (pdwIds != NULL) HeapFree(hHeap, 0, pdwIds); // выделяем память для массива идентификаторов pdwIds = (DWORD *)HeapAlloc(hHeap, 0, cbAlloc); if (pdwIds == NULL) { FreeLibrary(hPsApi); return SetLastError(ERROR_NOT_ENOUGH_MEMORY), FALSE; } // получаем идентификаторы процессов if (!_EnumProcesses(pdwIds, cbAlloc, &cbReturned)) { dwError = GetLastError(); HeapFree(hHeap, 0, pdwIds); FreeLibrary(hPsApi); return SetLastError(dwError), FALSE; } } while (cbReturned == cbAlloc); // теперь перечисляем все идентификаторы в массиве и вызываем // пользовательскую функцию для каждого процесса for (DWORD i = 0; i < cbReturned / sizeof(DWORD); i++) { BOOL bContinue; DWORD dwProcessId = pdwIds[i]; // обрабатываем два специальных случая: процесс Idle (0) и // процесс System (8) if (dwProcessId == 0) { bContinue = pfnEnumProc(0, _T("Idle"), lParam); } else if (dwProcessId == dwSystemId) { bContinue = pfnEnumProc(dwSystemId, _T("System"), lParam); } else { HANDLE hProcess; HMODULE hExeModule; DWORD cbNeeded; TCHAR szModulePath[MAX_PATH]; LPTSTR pszProcessName = NULL; // открываем описатель процесса hProcess = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, FALSE, dwProcessId); if (hProcess != NULL) { if (_EnumProcessModules(hProcess, &hExeModule, sizeof(HMODULE), &cbNeeded)) { if (_GetModuleFileNameEx(hProcess, hExeModule, szModulePath, MAX_PATH)) { pszProcessName = _tcsrchr(szModulePath, _T('\\')); if (pszProcessName == NULL) pszProcessName = szModulePath; else pszProcessName++; } } } CloseHandle(hProcess); // вызываем пользовательскую функцию bContinue = pfnEnumProc(dwProcessId, pszProcessName, lParam); } if (!bContinue) break; } HeapFree(hHeap, 0, pdwIds); FreeLibrary(hPsApi); return TRUE; } |
Обратите внимание, что мы связываемся с PSAPI.DLL динамически, загружая библиотеку с помощью LoadLibrary и получая адреса необходимых функций посредством GetProcAddress. Это позволит нам в дальнейшем включить эту функцию в программу, которая должна выполняться в том числе и на Windows 9x/Me, где PSAPI.DLL отстутствует. При реализации других способов перечисления процессов мы поступаем таким же образом. |
Функция EnumProcesses не позволяет узнать, сколько места нужно для того, чтобы принять весь массив идентификаторов. Поэтому мы вызываем ее в цикле, увеличивая размер буфера, до тех пор, пока размер возвращенного массива не будет меньше размера буфера.
Поскольку мы хотим помимо идентификаторов процессов получить и имена процессов, мы должны проделать дополнительную работу. Для каждого процесса мы сначала получаем его описатель (handle) c помощью функции OpenProcess и затем используем функцию ЕnumProcessModules, которая возвращает список модулей, загруженных в адресное пространство процесса. Первым модулем в списке всегда является модуль, соответсвующий EXE-файлу программы. Наконец, мы вызываем функцию GetModuleFileNameEx (которая также является частью PSAPI), чтобы получить путь к EXE-файлу по описателю модуля. Мы используем имя EXE-файла без пути в качестве имени процесса.
Oбратите внимание на специальную обработку для двух процессов. Мы вынуждены обрабатывать отдельно процесс бездействия системы (Idle) с идентификатором 0, и системный процесс (System), который имеет идентификатор 2 на Windows NT 4 и 8 - на Windows 2000/XP, потому что OpenProcess не позволяет открыть описатель для этих процессов, возвращая код ошибки ERROR_ACCESS_DENIED.
Если вы запустите тестовое приложение, прилагающееся к этой статье, то обнаружите, что для одного из процессов в качестве имени возвращается "(name unavailable)". Запустив Task Manager нетрудно определить, что этим процессом является CSRSS.EXE. Оказывается, система устанавливает такой дескиптор безопасности этому процессу, который не позволяет открыть его с необходимыми нам правами доступа.
С одной стороны, мы могли бы поступить с этим процессом так же, как с процессами Idle и System, однако нет уверенности, что идентификатор этого процесса является постоянным. С другой стороны, проблему можно решить, включив (enable) привилегию SE_DEBUG_NAME. Когда включена эта привилегия, вызывающий поток может открывать процессы с любыми флагами доступа, независимо от того, какой дескриптор безопасности назначен процессу. Поскольку эта привилегия открывет возможности по преодолению системы безопасности Windows NT, ее имеют лишь администраторы компьютера, и мы не стали добавлять код для включения привилегии непосредственно в функцию EnumProcesses_PsApi. Если необходимо, то привилегия может быть включена перед вызовом EnumProcesses_PsApi, и тогда функция вернет имя и для процесса CSRSS.EXE.
Корпорация Microsoft добавила набор функций под названием ToolHelp API в Windows 3.1, чтобы позволить сторонним разработчикам получить доступ к системной информации, которая ранее была доступна только программистам Microsoft. При создании Windows 95, эти функции перекочевали в новую систему под названием ToolHelp32 API. Операционная система Windows NT c cамого создания содержала средства для получения подобной информации под названием "данные производительности". Интерфейс для доступа к данным производительности был крайне запутанным и неудобным (справедливости ради надо отметить, что начиная с Windows NT 4.0, Microsoft предоставляет библиотеку Performance Data Helper, значительно облегчающую получение данных производительности; мы воспользуемся этой библиотекой при реализации соответствующего метода перечисления процессов). Говорят, команда Windows NT долгое время сопротивлялась включению ToolHelp32 API в систему, тем не менее, начиная с Windows 2000, ToolHelp32 API присутствует и в этой операционной системе.
Используя ToolHelp32 API, мы сначала создаем моментальный снимок (snapshot) списка процессов с помощью функции CreateToolhelp32Snapshot, а затем проходим по списку используя функции Process32First и Process32Next. Структура PROCESSENTRY32, заполняемая этими функциями, содержит всю необходимую информацию. Ниже приведен код функции EnumProcesses_ToolHelp, реализующей перечисление процессов с помощью ToolHelp32.
#include <tlhelp32.h> BOOL WINAPI EnumProcesses_ToolHelp( IN LPCTSTR pszMachineName, IN PFNENUMPROC pfnEnumProc, IN LPARAM lParam ) { _ASSERTE(pfnEnumProc != NULL); if (pszMachineName != NULL) return SetLastError(ERROR_INVALID_PARAMETER), FALSE; HINSTANCE hKernel; HANDLE (WINAPI * _CreateToolhelp32Snapshot)(DWORD, DWORD); BOOL (WINAPI * _Process32First)(HANDLE, PROCESSENTRY32 *); BOOL (WINAPI * _Process32Next)(HANDLE, PROCESSENTRY32 *); // получаем описатель KERNEL32.DLL hKernel = GetModuleHandle(_T("kernel32.dll")); _ASSERTE(hKernel != NULL); // находим необходимые функции в KERNEL32.DLL *(FARPROC *)&_CreateToolhelp32Snapshot = GetProcAddress(hKernel, "CreateToolhelp32Snapshot"); #ifdef UNICODE *(FARPROC *)&_Process32First = GetProcAddress(hKernel, "Process32FirstW"); *(FARPROC *)&_Process32Next = GetProcAddress(hKernel, "Process32NextW"); #else *(FARPROC *)&_Process32First = GetProcAddress(hKernel, "Process32First"); *(FARPROC *)&_Process32Next = GetProcAddress(hKernel, "Process32Next"); #endif if (_CreateToolhelp32Snapshot == NULL || _Process32First == NULL || _Process32Next == NULL) return SetLastError(ERROR_PROC_NOT_FOUND), FALSE; HANDLE hSnapshot; PROCESSENTRY32 Entry; // создаем моментальный снимок hSnapshot = _CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) return FALSE; // получаем информацию о первом процессе Entry.dwSize = sizeof(Entry); if (!_Process32First(hSnapshot, &Entry)) return FALSE; // перечисляем остальные процессы do { LPTSTR pszProcessName = NULL; if (Entry.dwSize > offsetof(PROCESSENTRY32, szExeFile)) { pszProcessName = _tcsrchr(Entry.szExeFile, _T('\\')); if (pszProcessName == NULL) pszProcessName = Entry.szExeFile; } if (!pfnEnumProc(Entry.th32ProcessID, pszProcessName, lParam)) break; Entry.dwSize = sizeof(Entry); } while (_Process32Next(hSnapshot, &Entry)); CloseHandle(hSnapshot); return TRUE; } |
Перед вызовом функций Process32First и Process32Next мы должны инициализировать поле dwSize структуры PROCESSENTRY32 таким образом, чтобы оно содержало размер структуры. Функции в свою очередь заносят в это поле количество байтов, записанных в структуру. Мы сравниваем это значение со смещением поля szExeFile в структуре, чтобы определить, было ли заполнено имя процесса.
Несмотря на наличие документированного способа получения списка процессов в Windows NT с помощью данных производительности, Windows NT Task Manager никогда не использовал этот интерфейс. Вместо этого он использовал недокументированную функцию ZwQuerySystemInformation, экспортируемую из NTDLL.DLL, которая позволяет получить доступ к самой разнообразной системной информации и списку процессов в том числе.
Функция ZwQuerySystemInformation имеет следующий прототип [2]:
NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation( IN SYSTEM_INFORMATION_CLASS SystemInformationClass, IN OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength ); |
где
SystemInformationClass | задает тип получаемой информации, нас интересует SystemProcessesAndThreadsInformation (5) |
SystemInformation | указатель на буфер, принимающий запрошенную информацию |
SystemInformationLength | задает длину приемного буфера в байтах |
ReturnLength | указатель на переменную, в которую заносится количество байтов, записанных в выходной буфер |
Формат информации о процессах и потоках описывается структурой SYSTEM_PROCESSES, которая содержит почти всю информацию, отображаемую Task Manager. Ниже привeден код функции EnumProcesses_NtApi, реализующей перечисление процессов с использованием функции ZwQuerySystemInformation.
typedef LONG NTSTATUS; typedef LONG KPRIORITY; #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) #define SystemProcessesAndThreadsInformation 5 typedef struct _CLIENT_ID { DWORD UniqueProcess; DWORD UniqueThread; } CLIENT_ID; typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING; typedef struct _VM_COUNTERS { SIZE_T PeakVirtualSize; SIZE_T VirtualSize; ULONG PageFaultCount; SIZE_T PeakWorkingSetSize; SIZE_T WorkingSetSize; SIZE_T QuotaPeakPagedPoolUsage; SIZE_T QuotaPagedPoolUsage; SIZE_T QuotaPeakNonPagedPoolUsage; SIZE_T QuotaNonPagedPoolUsage; SIZE_T PagefileUsage; SIZE_T PeakPagefileUsage; } VM_COUNTERS; typedef struct _SYSTEM_THREADS { LARGE_INTEGER KernelTime; LARGE_INTEGER UserTime; LARGE_INTEGER CreateTime; ULONG WaitTime; PVOID StartAddress; CLIENT_ID ClientId; KPRIORITY Priority; KPRIORITY BasePriority; ULONG ContextSwitchCount; LONG State; LONG WaitReason; } SYSTEM_THREADS, * PSYSTEM_THREADS; typedef struct _SYSTEM_PROCESSES { ULONG NextEntryDelta; ULONG ThreadCount; ULONG Reserved1[6]; LARGE_INTEGER CreateTime; LARGE_INTEGER UserTime; LARGE_INTEGER KernelTime; UNICODE_STRING ProcessName; KPRIORITY BasePriority; ULONG ProcessId; ULONG InheritedFromProcessId; ULONG HandleCount; ULONG Reserved2[2]; VM_COUNTERS VmCounters; #if _WIN32_WINNT >= 0x500 IO_COUNTERS IoCounters; #endif SYSTEM_THREADS Threads[1]; } SYSTEM_PROCESSES, * PSYSTEM_PROCESSES; BOOL WINAPI EnumProcesses_NtApi( IN LPCTSTR pszMachineName, IN PFNENUMPROC pfnEnumProc, IN LPARAM lParam ) { _ASSERTE(pfnEnumProc != NULL); if (pszMachineName != NULL) return SetLastError(ERROR_INVALID_PARAMETER), FALSE; HINSTANCE hNtDll; NTSTATUS (WINAPI * _ZwQuerySystemInformation)(UINT, PVOID, ULONG, PULONG); // получаем описатель NTDLL.DLL hNtDll = GetModuleHandle(_T("ntdll.dll")); _ASSERTE(hNtDll != NULL); // находим адрес ZwQuerySystemInformation *(FARPROC *)&_ZwQuerySystemInformation = GetProcAddress(hNtDll, "ZwQuerySystemInformation"); if (_ZwQuerySystemInformation == NULL) return SetLastError(ERROR_PROC_NOT_FOUND), FALSE; // получаем описатель кучи процесса по умолчанию HANDLE hHeap = GetProcessHeap(); NTSTATUS Status; ULONG cbBuffer = 0x8000; PVOID pBuffer = NULL; // трудно заранее определить, какой размер выходного // буфера будет достаточным, поэтому мы начинам с буфера // размером 32K и увеличиваем его по необходимости do { pBuffer = HeapAlloc(hHeap, 0, cbBuffer); if (pBuffer == NULL) return SetLastError(ERROR_NOT_ENOUGH_MEMORY), FALSE; Status = _ZwQuerySystemInformation( SystemProcessesAndThreadsInformation, pBuffer, cbBuffer, NULL); if (Status == STATUS_INFO_LENGTH_MISMATCH) { HeapFree(hHeap, 0, pBuffer); cbBuffer *= 2; } else if (!NT_SUCCESS(Status)) { HeapFree(hHeap, 0, pBuffer); return SetLastError(Status), FALSE; } } while (Status == STATUS_INFO_LENGTH_MISMATCH); PSYSTEM_PROCESSES pProcesses = (PSYSTEM_PROCESSES)pBuffer; for (;;) { PCWSTR pszProcessName = pProcesses->ProcessName.Buffer; if (pszProcessName == NULL) pszProcessName = L"Idle"; #ifdef UNICODE if (!pfnEnumProc(pProcesses->ProcessId, pszProcessName, lParam)) break; #else CHAR szProcessName[MAX_PATH]; WideCharToMultiByte(CP_ACP, 0, pszProcessName, -1, szProcessName, MAX_PATH, NULL, NULL); if (!pfnEnumProc(pProcesses->ProcessId, szProcessName, lParam)) break; #endif if (pProcesses->NextEntryDelta == 0) break; // find the address of the next process structure pProcesses = (PSYSTEM_PROCESSES)(((LPBYTE)pProcesses) + pProcesses->NextEntryDelta); } HeapFree(hHeap, 0, pBuffer); return TRUE; } |
При вызове ZwQuerySystemInformation трудно заранее определить, какой размер выходного буфера будет достаточным, поэтому мы начинам с буфера размером 32K и увеличиваем его по необходимости.
Структура SYSTEM_PROCESSES имеет переменный размер, поле NextEntryDelta указывает смещение к следующей струтктуре в массиве. Наличие этого поля позволяет нам игнорировать различия в размере фиксированной части структуры между Windows NT 4.0 и Windows 2000, где в состав структуры было добавлено поле IoCounters.
Как уже отмечалось, операционная система Windows NT с самого создания содержала интерфейс для получения разнообразной информации о системе в виде счетчиков производительности. Этот интерфейс является, скажем, далеко не интуитивным. Для получения той или иной информации нужно прочитать из ключа реестра HKEY_PERFORMANCE_DATA значение со специально сформированным именем. В результате возвращается набор глубоко вложенных структур, многие из которых переменного размера, и разбор этих данных требует определенной усидчивости.
Ситуация изменилась в лучшую сторону с появлением в Windows NT 4.0 библиотеки Performance Data Helper (PDH), которая предоставляет более удобный интерфейс к данным производительности. Эта библиотека, однако, не входила в комплект поставки Windows NT 4.0, она распространялась в составе Microsoft Platform SDK. В Windows 2000 PDH.DLL присутствует по умолчанию.
Подробное рассмотрение данных производительности выходит за рамки данной статьи, отметим лишь, что система подсчета производительности в Windows NT определяет понятие объекта, для которого осуществляется подсчет производительности. Примерами объектов являются процессор и жесткий диск. Каждый объект может иметь один или более экземпляров, и для каждого объекта существует свой набор счетчиков производительности. Наша задача состоит в перечислении всех экземпляров объекта с именем "Process" и получении для каждого из них значения счетчика с именем "ID Process". Ниже приведен код функции EnumProcesses_PerfData, которая использует PDH для перечисления процессов.
#include <pdh.h> #include <pdhmsg.h> BOOL WINAPI EnumProcesses_PerfData( IN LPCTSTR pszMachineName, IN PFNENUMPROC pfnEnumProc, IN LPARAM lParam ) { _ASSERTE(pfnEnumProc != NULL); HINSTANCE hPdh; PDH_STATUS (WINAPI * _PdhOpenQuery)(LPCWSTR, DWORD_PTR, HQUERY *); PDH_STATUS (WINAPI * _PdhAddCounter)(HQUERY, LPCWSTR, DWORD_PTR, HCOUNTER *); PDH_STATUS (WINAPI * _PdhCollectQueryData)(HQUERY); PDH_STATUS (WINAPI * _PdhGetRawCounterArray)(HCOUNTER, LPDWORD, LPDWORD, PDH_RAW_COUNTER_ITEM_W *); PDH_STATUS (WINAPI * _PdhCloseQuery)(HQUERY); // загружаем PDH.DLL hPdh = LoadLibrary(_T("pdh.dll")); if (hPdh == NULL) return FALSE; // находим необходимые функции в PDH.DLL *(FARPROC *)&_PdhOpenQuery = GetProcAddress(hPdh, "PdhOpenQueryW"); *(FARPROC *)&_PdhAddCounter = GetProcAddress(hPdh, "PdhAddCounterW"); *(FARPROC *)&_PdhGetRawCounterArray = GetProcAddress(hPdh, "PdhGetRawCounterArrayW"); *(FARPROC *)&_PdhCollectQueryData = GetProcAddress(hPdh, "PdhCollectQueryData"); *(FARPROC *)&_PdhCloseQuery = GetProcAddress(hPdh, "PdhCloseQuery"); // проверяем, что были найдены все функции if (_PdhOpenQuery == NULL || _PdhAddCounter == NULL || _PdhCollectQueryData == NULL || _PdhGetRawCounterArray == NULL || _PdhCloseQuery == NULL) { FreeLibrary(hPdh); return SetLastError(ERROR_PROC_NOT_FOUND), FALSE; } PDH_STATUS Status; WCHAR szCounterPath[1024]; // формируем путь к счетчику производительности Status = PerfFormatCounterPath(hPdh, pszMachineName, szCounterPath, 1024); if (Status != ERROR_SUCCESS) { FreeLibrary(hPdh); return SetLastError(Status), FALSE; } HQUERY hQuery; HCOUNTER hCounter; // открываем запрос Status = _PdhOpenQuery(NULL, 0, &hQuery); if (Status != ERROR_SUCCESS) { FreeLibrary(hPdh); return SetLastError(Status), FALSE; } // добавляем счетчик к запросу Status = _PdhAddCounter(hQuery, szCounterPath, 0, &hCounter); if (Status != ERROR_SUCCESS) { _PdhCloseQuery(hQuery); FreeLibrary(hPdh); return SetLastError(Status), FALSE; } // получаем текущие значения счетчика Status = _PdhCollectQueryData(hQuery); if (Status != ERROR_SUCCESS) { _PdhCloseQuery(hQuery); FreeLibrary(hPdh); return SetLastError(Status), FALSE; } DWORD cbSize = 0; DWORD cItems = 0; HANDLE hHeap = GetProcessHeap(); // определяем необходимый размер буфера Status = _PdhGetRawCounterArray(hCounter, &cbSize, &cItems, NULL); if (Status != ERROR_SUCCESS) { _PdhCloseQuery(hQuery); FreeLibrary(hPdh); return SetLastError(Status), FALSE; } // выделяем память для буфера PDH_RAW_COUNTER_ITEM_W * pRaw = (PDH_RAW_COUNTER_ITEM_W *)HeapAlloc(hHeap, 0, cbSize); if (pRaw == NULL) { _PdhCloseQuery(hQuery); FreeLibrary(hPdh); return SetLastError(ERROR_NOT_ENOUGH_MEMORY), FALSE; } // получаем значения счетчика Status = _PdhGetRawCounterArray(hCounter, &cbSize, &cItems, pRaw); // закрываем запрос _PdhCloseQuery(hQuery); if (Status != ERROR_SUCCESS) { HeapFree(hHeap, 0, pRaw); FreeLibrary(hPdh); return SetLastError(Status), FALSE; } // перечисляем все экземпляры for (DWORD i = 0; i < cItems; i++) { DWORD dwProcessId = (DWORD)pRaw[i].RawValue.FirstValue; LPCTSTR pszProcessName; #ifdef UNICODE pszProcessName = pRaw[i].szName; #else CHAR szProcessName[MAX_PATH]; WideCharToMultiByte(CP_ACP, 0, pRaw[i].szName, -1, szProcessName, MAX_PATH, NULL, NULL); pszProcessName = szProcessName; #endif // исключаем экземпляр с именем _Total if (dwProcessId == 0 && lstrcmp(pszProcessName, _T("_Total")) == 0) continue; if (!pfnEnumProc(dwProcessId, pszProcessName, lParam)) break; } HeapFree(hHeap, 0, pRaw); FreeLibrary(hPdh); return TRUE; } |
Мы возвращаем имя экземпляра объекта в качестве имени процесса. Если вы запустите тестовую программу, то увидите, что имена экземпляров соответсвуют именам исполняемых файлов процессов без расширения.
Названия объектов и счетчиков производительности являются локализуемыми. Это значит, что, например, на русской версии Windows NT необходимо использовать "Процесс" и "Идентификатор процесса" вместо "Process" и "ID Process". Для получения локализованных имен объектов и формирования полного пути к интересующему нас счетчику производительности служит вспомогательная функция PerfFormatCounterPath, исходный код которой приведен ниже.
static PDH_STATUS WINAPI PerfFormatCounterPath( IN HINSTANCE hPdh, IN LPCTSTR pszMachineName, IN LPWSTR pszPath, IN ULONG cchPath ) { _ASSERTE(hPdh != NULL); _ASSERTE(_CrtIsValidPointer(pszPath, cchPath * sizeof(WCHAR), 1)); PDH_STATUS (WINAPI * _PdhMakeCounterPath)( PDH_COUNTER_PATH_ELEMENTS_W *, LPWSTR, LPDWORD, DWORD); PDH_STATUS (WINAPI * _PdhLookupPerfNameByIndex)( LPCWSTR, DWORD, LPWSTR, LPDWORD); *(FARPROC *)&_PdhMakeCounterPath = GetProcAddress(hPdh, "PdhMakeCounterPathW"); *(FARPROC *)&_PdhLookupPerfNameByIndex = GetProcAddress(hPdh, "PdhLookupPerfNameByIndexW"); if (_PdhMakeCounterPath == NULL || _PdhLookupPerfNameByIndex == NULL) return ERROR_PROC_NOT_FOUND; PDH_STATUS Status; DWORD cbName; WCHAR szObjectName[80]; WCHAR szCounterName[80]; LPWSTR pszMachineW = NULL; #ifndef _UNICODE WCHAR szMachineName[256]; if (pszMachineName != NULL) { MultiByteToWideChar(CP_ACP, 0, pszMachineName, -1, szMachineName, 256); pszMachineW = szMachineName; } #else pszMachineW = (LPWSTR)pszMachineName; #endif // находим имя объекта Process cbName = sizeof(szObjectName); Status = _PdhLookupPerfNameByIndex(pszMachineW, 230, szObjectName, &cbName); if (Status != ERROR_SUCCESS) return Status; // находим имя счетчика ID Process cbName = sizeof(szCounterName); Status = _PdhLookupPerfNameByIndex(pszMachineW, 784, szCounterName, &cbName); if (Status != ERROR_SUCCESS) return Status; PDH_COUNTER_PATH_ELEMENTS_W pcpe; pcpe.szMachineName = pszMachineW; pcpe.szObjectName = szObjectName; pcpe.szInstanceName = L"*"; pcpe.szParentInstance = NULL; pcpe.dwInstanceIndex = 0; pcpe.szCounterName = szCounterName; // формируем полный путь к счетчику return _PdhMakeCounterPath(&pcpe, pszPath, &cchPath, 0); } |
Мы используем функцию PdhLookupPerfNameByIndex, чтобы получить локализованные имена по их индексам. Для стандартных объектов и счетчиков производительности индексы являются постоянными и не зависят от версии операционной системы, поэтому вполне безопасно задать значения индексов непосредственно в коде.
Отметим, что из перечисленных выше методов, этот метод - первый, который позволяет перечислить процессы на другом компьютере. Все что нужно сделать - это указать имя компьютера в поле szMachineName структуры PDH_COUNTER_PATH_ELEMENTS.
Windows Management Instrumentation (WMI) является реализацией Mircrosoft для так называемой технологии Web-Based Enterprise Management (WBEM). WBEM определяет унифицированную архитектуру, которая позволяет получать данные от различных источников, построенных с помощью различных технологий и платформ, и единообразно представлять эти данные. WBEM основана на схеме общей информационной модели (Common Information Model, CIM), которая является индустриальным стандартом, управляемым Distributed Management Task Force (DMTF). WMI поставляется в составе Windows 2000, но также может быть установлен на Windows 95/98/Me и Windows NT 4.0.
Сколько-либо подробное рассмотрение WMI выходит за рамки этой статьи. Ограничимся лишь кодом, который перечисляет процессы с использованием интерфейсов, предоставляемых WMI.
#include <comdef.h> #include <SshWbemHelpers.h> BOOL WINAPI EnumProcesses_Wmi( IN LPCTSTR pszMachineName, IN PFNENUMPROC pfnEnumProc, IN LPARAM lParam ) { _ASSERTE(pfnEnumProc != NULL); try { HRESULT hRes; TCHAR szServer[256]; _bstr_t bstrServer; IWbemLocatorPtr spLocator; IWbemServicesPtr spServices; IEnumWbemClassObjectPtr spEnum; IWbemClassObjectPtr spObject; ULONG ulCount; // создаем объект WBEM locator hRes = CoCreateInstance(__uuidof(WbemLocator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IWbemLocator), (PVOID *)&spLocator); if (FAILED(hRes)) _com_issue_error(hRes); if (pszMachineName == NULL) lstrcpy(szServer, _T("root\\cimv2")); else wsprintf(szServer, _T("\\\\%s\\root\\cimv2"), pszMachineName); // соединяемся с указанной машиной hRes = spLocator->ConnectServer(_bstr_t(L"root\\cimv2"), NULL, NULL, NULL, 0, NULL, NULL, &spServices); if (FAILED(hRes)) _com_issue_error(hRes); // создаем перечислитель процессов hRes = spServices->CreateInstanceEnum(_bstr_t(L"Win32_Process"), WBEM_FLAG_SHALLOW|WBEM_FLAG_FORWARD_ONLY, NULL, &spEnum); if (FAILED(hRes)) _com_issue_error(hRes); // перечисляем процессы while (spEnum->Next(WBEM_INFINITE, 1, &spObject, &ulCount) == S_OK) { _variant_t valId; _variant_t valName; // получаем идентификатор процесса hRes = spObject->Get(L"ProcessId", 0, &valId, NULL, NULL); if (FAILED(hRes)) { spObject = NULL; continue; } // получаем имя процесса hRes = spObject->Get(L"Name", 0, &valName, NULL, NULL); if (FAILED(hRes)) { spObject = NULL; continue; } _ASSERTE(valId.vt == VT_I4); _ASSERTE(valName.vt == VT_BSTR); if (!pfnEnumProc(valId.lVal, (LPCTSTR)_bstr_t(valName), lParam)) break; spObject = NULL; } } catch (_com_error& err) { return SetLastError(err.Error()), FALSE; } return TRUE; } |
Поскольку WMI основана на технологии COM, это избавило нас от необходимости явно загружать требуемые библиотеки, как это мы делали в предыдущих примерах. Этот же факт требует инициализации библиотеки COM прежде чем будет вызвана функция перечисления процессов. В приложении, созданном с использованием MFC, это можно сделать с помощью функции AfxOleInit, в остальных случаях следует пользоваться функциями CoInitialize или CoInitializeEx.
Кроме того, использование WMI требует инициализации безопасности COM с помощью функции CoInitializeSecurity:
CoInitializeSecurity( NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, 0); |
Заметим, что как и метод с использованием счетчиков производительности, этот метод позволяет перечислить процессы на другом компьютере, для чего нужно указать имя компьютера в вызове IWbemLocator::ConnectServer.
Перечисление процессов на другом компьютере в том виде, в котором оно представлено в последних двух примерах, будет работать только если текущий пользователь локального компьютера включен в группу администраторов удаленного компьютера. Приведенные выше примеры не позволяют изменить имя пользователя и пароль, с которыми производится подключение. Чтобы указать альтернативные имя пользователя и пароль, для PDH следует использовать функцию NetUseAdd или WNetAddConnection2 чтобы установить аутентифицированное соединение с ресурсом IPC$ на удаленном компьютере. Для WMI следует использовать стандартные средства COM. Подробное рассмотрение этих механизмов выходит за рамки данной статьи. |
Таким образом, мы рассмотрели пять различных способов получения списка процессов. Ни один из них не является универсальным, так как существует версия Windows, где этот метод не работает. Приведенная ниже таблица показывает применимость различных методов.
Windows 9x/Me | Windows NT 4.0 | Windows 2000/XP | |
Способ 1 | Нет | Да* | Да |
Способ 2 | Да | Нет | Да |
Способ 3 | Нет | Да | Да |
Способ 4 | Нет | Да* | Да |
Способ 5 | Да* | Да* | Да |
Используя эту таблицу и приведенные выше функции, несложно написать функцию перечисления процессов, которая будет работать на всех версиях Windows. Например, если мы хотим использовать PSAPI при работе на Windows NT/2000/XP и ToolHelp32 API - на Windows 9x/Me, то функция будет выглядеть следующим образом:
BOOL WINAPI MyEnumProcesses( IN PFNENUMPROC pfnEnumProc, IN LPARAM lParam ) { OSVERSIONINFO osvi; osvi.dwOSVersionInfoSize = sizeof(osvi); if (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) return EnumProcesses_ToolHelp(pfnEnumProc, lParam); else if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) return EnumProcesses_PsApi(pfnEnumProc, lParam); else return SetLastError(ERROR_CALL_NOT_IMPLEMENTED), FALSE; } |
Эту статью сопровождает небольшое тестовое приложение с амбициозным названием Process Viewer, которое демонстрирует все перечисленные выше способы перечисления процессов. Реализация всех способов находится в файлах enumproc.h и enumproc.cpp. Мы постарались оформить эти файлы таким образом, чтобы максимально облегчить их включение в другие проекты. Для сборки приложения необходимо иметь установленным Microsoft Platform SDK.
Сообщений 12 Оценка 512 Оценить |