C/C++: Пишем процесс-менеджер

Николай "G" Андреев

(gorlum@real.xakep.ru)

Процесс... Как много в этом звуке... Я еще помню времена, когда у меня стоял Windows 95, и на компьютере творился сплошной хаос. Стоило зависнуть одной программе, как сразу вылетали все форточки. Стандартный таскменеджер не просто не справлялся с работой, он сам порой являлся причиной сбоя. Тогда-то я и задумался о том, как бы получить над системой больше власти. Облазив кучу ресурсов в инете и не найдя ничего путного, я понял, что надо писать свой менеджер, имеющий больше возможностей, чем обычный, и самое главное – стабильно работающий. И я его написал, причем пользуюсь им по сей день. А вот как он был создан - этим я поделюсь с тобой в этой статье.

Мы научимся не только перечислять и выводить на экран все запущенные на данный момент процессы, но и по-разному манипулировать ими. Научимся "завершать задачу", менять приоритет процесса, его свойства и получать о нем любую информацию. Все эти навыки могут очень пригодиться при написании программ под windows.

Кодим

Сразу к делу. Сначала создадим форму и кинем на нее объект ListBox и объект Button. В листбокс запишем все текущие процессы, а кнопочка Button даст нам возможность убивать эти процессы. В коде мы создадим соответствующую оконную процедуру для обработки сообщений (например, сообщения о нажатии клавиши). Как это сделать, я не буду здесь описывать, просто загляни в майский выпуск твоего любимого журнала :).

Способов перечислить процессы много. В Windows NT 4.0, например, доступен только один - воспользоваться функцией EnumProcesses из набора Process Status Application Programming Interface (в простонародье PSAPI). Он также доступен и в старших НТях, а в 9x - нет, так что, хотя этот способ и очень удобен, он, увы, отпадает. Есть способ порыться в реестре, в скрытом разделе HKEY_PERFOMANCE_DATA, но он также отпадает из-за сложности реализации. На наше счастье, яйцеголовые из Microsoft придумали универсальный способ и дали ему название ToolHelp API.

Для использования этого способа подключим заголовочный файл tlhelp32.h к нашей программе. Он находится в стандартной поставке Platform SDK (прилагающейся к студии), но если вдруг компилятор его не найдет, посмотри на нашем диске - я его приложил к программе. Для перечисления процессов мы будем использовать три функции:

CreateToolhelp32Snapshot - для получения "снимка" процессов;

Process32First - для получения первого процесса из снимка;

Process32Next - для получения оставшихся процессов.

Получаем процессы

При перечислении мы должны добавить имя процесса в листбокс на форме и идентификатор этого процесса в массив. Это необходимо для того, чтобы, связав значение в массиве со значением в списке, мы могли манипулировать процессом. Например, выделить определенный процесс в списке и нажать на кнопку "завершить", что, по идее, должно привести к завершению выбранного процесса.

Итак, определим переменные: массив типа DWORD из, допустим, 1024 элементов и переменную типа PROCESSENTRY32, в которую у нас будут заноситься значения свойств и характеристик процесса.

Структура PROCESSENTRY32

typedef struct tagPROCESSENTRY32 {

DWORD dwSize;

DWORD cntUsage;

DWORD th32ProcessID;

ULONG_PTR th32DefaultHeapID;

DWORD th32ModuleID;

DWORD cntThreads;

DWORD th32ParentProcessID;

LONG pcPriClassBase;

DWORD dwFlags;

TCHAR szExeFile[MAX_PATH];

} PROCESSENTRY32;

typedef PROCESSENTRY32 *PPROCESSENTRY32;

Создадим снимок процесса и начнем его исследовать. Воспользуемся для этого функцией CreateToolhelp32Snapshot, о которой я уже говорил раньше. В первом параметре мы передадим ей константу TH32CS_SNAPPROCESS, говорящую о нашем желании получить снимок именно процессов, а не модулей, нитей или чего-нибудь другого в этом роде. Второй параметр нас совершенно не интересует, поэтому передаем NULL. Функция вернет значение типа HANDLE, которое мы сохраним в какую-нибудь переменную и будем впоследствии его использовать.

Получив снимок, можно начать перечислять процессы при помощи функции Process32First, в первом параметре которой мы передадим полученный HANDLE снимка, а во втором ссылку на переменную типа PROCESSENTRY32. Далее создадим цикл do - while, его условием является равенство нулю функции Process32Next. Там же в цикле будем заносить в массив DWORD значение члена th32ProcessID нашей структуры, увеличивать счетчик на 1 и ставить имя процесса в листбокс. В коде это выглядит так:

// process - наша структура

idarray[i++] = process.th32ProcessID;

SendDlgItemMessage(hwnd, IDC_LISTBOX1, LB_ADDSTRING,

0, (LPARAM)process.szExeFile);

SendDlgItemMessage(hwnd, IDC_LISTBOX1, LB_SETCURSEL,

(WPARAM)SendDlgItemMessage(hw, IDC_LISTBOX1, LB_GETCOUNT, 0, 0)-1, 0

где hwnd - описание нашего окна (формы), а IDC_LISTBOX1 - идентификатор листбокса. В итоге, для того, чтобы перечислить процессы и сообщить о них нашему таскменеджеру, необходимо всего лишь выполнить этот цикл. Но системные процессы не дадут доступа к своим свойствам, и мы не сможем ни закрыть их, ни даже посмотреть их имена. Как это исправить? Надо повысить привилегии процесса-менеджера до уровня отладчика. Звучит сложно, но на самом деле - раз плюнуть. Я написал небольшую функцию для изменения собственных привилегий. Запусти ее с параметром true, и получишь уровень отладчика, с false - ты его, соответственно, лишишься.

Листинг функции, дающей привилегии отладчика нашей программе

void EnableDebugPrivilege(bool fEnable){

HANDLE hToken;

if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)){

TOKEN_PRIVILEGES tp;

tp.PrivilegeCount = 1;

LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);

tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0;

AdjustTokenPrivileges(hToken, false, &tp, sizeof(tp), NULL, NULL);

CloseHandle(hToken);

}

}

Убиваем процессы

Хорошо, какие процессы есть в системе - мы знаем, но как ими манипулировать? Для этого нам нужно получить хэндл процесса. Только получив его, можно с ним что-нибудь сотворить. Делается это с помощью функции OpenProcess, передав которой параметры PROCESS_ALL_ACCESS, false и идентификатор процесса (из массива), получим дескриптор. Поскольку мы пишем менеджер процессов, то первое, что он должен уметь - эти процессы убивать (завершать). Завершить их совсем не сложно. Достаточно запустить функцию TerminateProcess с параметрами: хэндл процесса и код завершения (я пишу 0). Функция завершит процесс, а так как привилегии у нас высокие, то убиваемый процесс умрет без лишних слов.

После того, как ты завершил работу с дескриптором процесса или любым другим хэндлом, стоит его закрыть с помощью функции CloseHandle.

Практика убийства процесса проста так же, как и теория. В нашей программе массив идентификаторов процессов и список связаны, так что при нажатии на кнопку завершения процесса необходимо будет вычислить номер выделенного в списке элемента и уже с ним работать. В итоге код завершения получился довольно компактный:

// вычисляем индекс выделенного элемента

int index = SendDlgItemMessage(hwnd, IDC_LISTBOX1, LB_GETCOUNT, 0, 0);

// открываем процесс из массива с этим индексом

HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS, false, idarray[index]);

// убиваем его

TerminateProcess(hProcess, 0);

// закрываем хэндл

CloseHandle(hProcess);

Break

Так уж вышло, что я терпеть не могу стандартные системные утилиты Windows и всегда стараюсь написать свои. Так получилось и с процесс-менеджером. Я описал самую примитивную программу для манипулирования процессами, так что для твоего творчества остается уйма места. Например, ты можешь заменить listbox на listview и добавить к именам процессов их иконки или, например, создать возможность изменения приоритетов процесса с помощью функции SetPriorityClass. Если возникли какие-нибудь вопросы, идеи или замечания - пиши. Постараюсь ответить.

На этом все. Удачного компилирования.

Мне часто пишут письма с просьбой объяснить, как работает та или иная утилита, или чем руководствоваться при написании системных программ. В последнее время при ответе на такие письма я стал ограничиваться всего лишь ссылкой на одну великую книгу: "Programming Applications for Windows" (русское название - "Windows для профессионалов"), автором которой является редактор Microsoft Press Джеффри Рихтер. Эта книга является библией системного программирования под Windows. В ней читатель может найти ответы на все вопросы, возникающие при написании приложений. Безусловно, огромный интерес при прочтении книги вызывает целая глава о перехвате API функций, о технологии, позволяющей как угодно манипулировать системой. Книга содержит в себе огромное количество прекрасно оформленных исходников и будет полезна любому начинающему или продвинутому кодеру.



Опубликовал admin
17 Дек, Среда 2003г.



Программирование для чайников.