Издание четвертое windows ® для профессионалов создание эффективных Win32-приложений с учетом специфики 64-разрядной версии Windows



Pdf просмотр
страница12/68
Дата28.11.2016
Размер3.57 Mb.
Просмотров12524
Скачиваний0
1   ...   8   9   10   11   12   13   14   15   ...   68
Функция TerminateProcess
Вызов функции
TerminateProcess тоже завершает процесс TerminateProcess(
HANDLE hProcess,
UINT Главное отличие этой функции отв том, что ее может вызвать любой потоки завершить любой процесс. Параметр
hProcess идентифицирует описатель завершаемого процесса, а в параметре
fuExitCode возвращается код завершения про цесса.
Пользуйтесь
TerminateProcess лишь в том случае, когда иным способом завершить процесс не удается. Процесс не получает абсолютно никаких уведомлений о том, что он завершается, и приложение не может ни выполнить очистку, ни предотвратить свое неожиданное завершение (если оно, конечно, не использует механизмы защиты. При этом теряются все данные, которые процесс не успел переписать из памяти на диск.
Процесс действительно не имеет ни малейшего шанса самому провести очистку,
но операционная система высвобождает все принадлежавшие ему ресурсы возвращает себе выделенную им память, закрывает любые открытые файлы, уменьшает счетчики соответствующих объектов ядра и разрушает все его User и GDI объекты.
По завершении процесса (неважно каким способом) система гарантирует после него ничего не останется — даже намеков на то, что он когда то выполнялся.
Завер
шенный процесс не оставляет за собой никаких следов. Надеюсь, я сказал ясно.
TerminateProcess — функция асинхронная, те. она сообщает системе, что Вы хотите завершить процесс, но к тому времени, когда она вернет управление,
процесс может быть еще не уничтожен. Так что, если Вам нужно точно знать момент завершения процесса, используйте
WaitForSingleObject (см. главу 9) или аналогичную функцию, передав ей описатель этого процесса.
Когда все потоки процесса уходят
В такой ситуации (а она может возникнуть, если все потоки вызвали
ExitThread или их закрыли вызовом
TerminateThread) операционная система больше не считает нужным содержать адресное пространство данного процесса. Обнаружив, что в процессе не исполняется ни один поток, она немедленно завершает его. При этом код завершения процесса приравнивается коду завершения последнего потока.

80
Ч АС Т Ь I I
НАЧИНАЕМ РАБОТАТЬ
Что происходит при завершении процесса
А происходит вот что:
1.
Выполнение всех потоков в процессе прекращается.
2.
Все User и GDI объекты, созданные процессом, уничтожаются, а объекты ядра закрываются (если их не использует другой процесс).
3.
Код завершения процесса меняется со значения STILL_ACTIVE на код, переданный вили TerminateProcess.
4.
Объект ядра процесс переходит в свободное, или незанятое (signaled), состояние. (Подробнее на эту тему см. главу 9.) Прочие потоки в системе могут приостановить свое выполнение вплоть до завершения данного процесса.
5.
Счетчик объекта ядра процесс уменьшается на Связанный с завершаемым процессом объект ядра не высвобождается, пока не будут закрыты ссылки на него и из других процессов. В момент завершения процесса система автоматически уменьшает счетчик пользователей этого объекта на 1, и объект разрушается, как только его счетчик обнуляется. Кроме того, закрытие процесса не приводит к автоматическому завершению порожденных им процессов.
По завершении процесса его код и выделенные ему ресурсы удаляются из памяти.
Однако область памяти, выделенная системой для объекта ядра процесс, не освобождается, пока счетчик числа его пользователей не достигнет нуля. А это произойдет, когда все прочие процессы, создавшие или открывшие описатели для ныне покойного процесса, уведомят систему (вызовом
CloseHandle) о том, что ссылки на этот процесс им больше не нужны.
Описатели завершенного процессса уже мало на что пригодны. Разве что родительский процесс, вызвав функцию
GetExitCodeProcess, может проверить, завершен ли процесс, идентифицируемый параметром
hProcess, и, если да, определить код завершения Код завершения возвращается как значение типа DWORD, на которое указывает
pdwExitCode. Если на момент вызова GetExitCodeProcess процесс еще не завершился, в заносится идентификатор STILL_ACTIVE (определенный как 0x103). А если он уничтожен, функция возвращает реальный код его завершения.
Вероятно, Вы подумали, что можно написать код, который, периодически вызывая функцию
GetExitCodeProcess и проверяя возвращаемое ею значение, определял бы момент завершения процесса. В принципе такой код мог бы сработать во многих ситуациях, но он был бы неэффективен. Как правильно решить эту задачу, я расскажу в следующем разделе.
Дочерние процессы
При разработке приложения часто бывает нужно, чтобы какую то операцию выполнял другой блок кода. Поэтому — хочешь, не хочешь — приходится постоянно вызывать функции или подпрограммы. Но вызов функции приводит к приостановке выполнения основного кода Вашей программы до возврата из вызванной функции. Альтернативный способ — передать выполнение какой то операции другому потоку в пределах данного процесса (поток, разумеется, нужно сначала создать. Это позво

81
Г ЛАВА 4
Процессы лит основному коду программы продолжить работу в то время, как дополнительный поток будет выполнять другую операцию. Прием весьма удобный, но, когда основному потоку потребуется узнать результаты работы другого потока, Вам не избежать проблем, связанных с синхронизацией.
Есть еще один прием Ваш процесс порождает дочерний и возлагает на него выполнение части операций. Будем считать, что эти операции очень сложны. Допустим,
для их реализации Вы просто создаете новый поток внутри того же процесса. Выпишете тот или иной код, тестируете его и получаете некорректный результат может, ошиблись в алгоритме или запутались в ссылках и случайно перезаписали какие нибудь важные данные в адресном пространстве своего процесса. Так вот, один из способов защитить адресное пространство основного процесса от подобных ошибок как рази состоит в том, чтобы передать часть работы отдельному процессу. Далее можно или подождать, пока он завершится, или продолжить работу параллельно с ним.
К сожалению, дочернему процессу, по видимому, придется оперировать сданными, содержащимися в адресном пространстве родительского процесса. Было бы неплохо, чтобы он работал исключительно в своем адресном пространстве, а в Вашем просто считывал нужные ему данные тогда он не сможет что то испортить в адресном пространстве родительского процесса. В Windows предусмотрено несколько способов обмена данными между процессами DDE (Dynamic Data Exchange), каналы (pipes), почтовые ящики (mailslots) и т. д. А один из самых удобных способов,
обеспечивающих совместный доступ к данным, — использование файлов, проецируемых в память (memory mapped files). (Подробнее на эту тему см. главу Если Вы хотите создать новый процесс, заставить его выполнить какие либо операции и дождаться их результатов, напишите примерно такой код pi;
DWORD dwExitCode;
// порождаем дочерний процесс fSuccess = CreateProcess(..., &pi);
if (fSuccess) {
// закрывайте описатель потока, как только необходимость в нем отпадает приостанавливаем выполнение родительского процесса пока не завершится дочерний процесс, INFINITE);
// дочерний процесс завершился получаем код его завершения, &dwExitCode);
// закрывайте описатель процесса, как только необходимость в нем отпадает!
CloseHandle(pi.hProcess);
}
В этом фрагменте кода мы создали новый процесс и, если это прошло успешно,
вызвали функцию
WaitForSingleObject:
DWORD WaitForSingleObject(HANDLE hObject, DWORD Подробное рассмотрение данной функции мы отложим до главы 9, а сейчас ограничимся одним соображением. Функция задерживает выполнение кода до тех пор,

82
Ч АС Т Ь I I
НАЧИНАЕМ РАБОТАТЬ
пока объект, определяемый параметром
hObject, не перейдет в свободное (незанятое)
состояние. Объект процесс переходит в такое состояние при его завершении. Поэтому вызов
WaitForSingleObject приостанавливает выполнение потока родительского процесса до завершения порожденного им процесса. Когда
WaitForSingleObject вернет управление, Вы узнаете код завершения дочернего процесса через функцию
Get
ExitCodeProcess.
Обращение кв приведенном выше фрагменте кода заставляет систему уменьшить значения счетчиков объектов потоки процесс до нуля и тем самым освободить память, занимаемую этими объектами.
Вы, наверное, заметили, что в этом фрагменте я закрыл описатель объекта ядра
«первичный поток (принадлежащий дочернему процессу) сразу после возврата из
CreateProcess. Это не приводит к завершению первичного потока дочернего процесса просто уменьшает счетчик, связанный с упомянутым объектом. А вот почему это делается — и, кстати, даже рекомендуется делать — именно так, станет ясно из простого примера. Допустим, первичный поток дочернего процесса порождает еще один потока сам после этого завершается. В этот момент система может высвободить объект первичный поток дочернего процесса из памяти, если у родительского процесса нет описателя данного объекта. Но если родительский процесс располагает таким описателем, система не сможет удалить этот объект из памяти до тех пор, пока и родительский процесс не закроет его описатель.
Запуск обособленных дочерних процессов
Что ни говори, но чаще приложение все таки создает другие процессы как
обособ
ленные (detached processes). Это значит, что после создания и запуска нового процесса родительскому процессу нет нужды с ним взаимодействовать или ждать, пока тот закончит работу. Именно таки действует Explorer: запускает для пользователя новые процессы, а дальше его уже не волнует, что там сними происходит.
Чтобы обрубить все пуповины, связывающие Explorer с дочерним процессом, ему нужно (вызовом
CloseHandle) закрыть свои описатели, связанные с новым процессом и его первичным потоком. Приведенный ниже фрагмент кода демонстрирует, как,
создав процесс, сделать его обособленным pi;
BOOL fSuccess = CreateProcess(..., &pi);
if (fSuccess) {
// разрешаем системе уничтожить объекты ядра "процесс" и "поток сразу после завершения дочернего процесса
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
Перечисление процессов, выполняемых в системе
Многие разработчики программного обеспечения пытаются создавать инструментальные средства или утилиты для Windows, требующие перечисления процессов,
выполняемых в системе. Изначально вне было функций, которые позволяли бы это делать. Однако введется постоянно обновляемая база данных Performance Data. В ней содержится чуть лине тонна информации, доступной через функции реестра вроде
RegQueryValueEx, для которой надо указать корне

83
Г ЛАВА 4
Процессы вой раздел HKEY_PERFORMANCE_DATA. Мало кто из программистов знает об этой базе данных, и причины тому кроются, намой взгляд, в следующем.
í
Для нее не предусмотрено никаких специфических функций нужно использовать обычные функции реестра.
í
Ее нет в Windows 95 и Windows Структура информации в этой базе данных очень сложна многие просто избегают ею пользоваться (и другим не советуют).
Чтобы упростить работу с этой базой данных, Microsoft создала набор функций под общим названием Performance Data Helper (содержащийся в PDH.dll). Если Вас интересует более подробная информация о библиотеке PDH.dll, ищите раздел по функциям Performance Data Helper в документации Platform Как я уже упоминал, в Windows 95 и Windows 98 такой базы данных нет. Вместо них предусмотрен набор функций, позволяющих перечислять процессы. Они включены в ToolHelp API. За информацией о них я вновь отсылаю Вас к документации SDK — ищите разделы по функциями Process32Next.
Но самое смешное, что разработчики Windows NT, которым ToolHelp функции явно не нравятся, не включили их в Windows NT. Для перечисления процессов они создали свой набор функций под общим названием Process Status (содержащийся в. Так что ищите в документации Platform SDK раздел по функции
Enum
Processes.
Microsoft, которая до сих пор, похоже, старалась усложнить жизнь разработчикам инструментальных средств и утилит, все же включила ToolHelp функции в Win dows 2000. Наконец то и эти разработчики смогут унифицировать свой код хотя бы для Windows 95, Windows 98 и Windows 2000!
Программа-пример ProcessInfo
Эта программа, «04 ProcessInfo.exe» (см. листинг на рис. 4 6), демонстрирует, как создать очень полезную утилиту на основе ToolHelp функций. Файлы исходного кода и ресурсов программы находятся в каталоге 04 ProcessInfo на компакт диске, прилагаемом к книге. После запуска ProcessInfo открывается окно, показанное на рис. 4 4.
ProcessInfo сначала перечисляет все процессы, выполняемые в системе, а затем выводит в верхний раскрывающийся список имена и идентификаторы каждого процесса. Далее выбирается первый процесс и информация о нем показывается в большом текстовом поле, доступном только для чтения. Как видите, для текущего процесса сообщается его идентификатор (вместе с идентификатором родительского процесса, класс приоритета и количество потоков, выполняемых в настоящий момент в контексте процесса. Объяснение большей части этой информации выходит за рамки данной главы, но будет рассматриваться в последующих главах.
При просмотре списка процессов становится доступен элемент меню VMMap. (Он отключается, когда Вы переключаетесь на просмотр информации о модулях) Выбрав элемент меню VMMap, Вы запускаете программу пример VMMap (см. главу 14). Эта программа проходит по адресному пространству выбранного процесса.
В информацию о модулях входит список всех модулей (EXE и DLL файлов, спро ецированных на адресное пространство текущего процесса. Фиксированным модулем) считается тот, который был неявно загружен при инициализации процесса. Для явно загруженных DLL показываются счетчики числа пользователей этих DLL. Во втором столбце выводится базовый адрес памяти, на который спроеци

84
Ч АС Т Ь I I
НАЧИНАЕМ РАБОТАТЬ
рован модуль. Если модуль размещен не по заданному для него базовому адресу, в скобках появляется и этот адрес. В третьем столбце сообщается размер модуля в байтах, а в последнем — полное (вместе с путем) имя файла этого модуля. И, наконец,
внизу показывается информация о потоках, выполняемых в данный момент в контексте текущего процесса. При этом отображается идентификатор потока (thread ID, и его приоритет.
Рис. 4-4.
ProcessInfo в действии
В дополнение к информации о процессах Вы можете выбрать элемент меню Modu les. Это заставит ProcessInfo перечислить все модули, загруженные в системе, и поместить их имена в верхний раскрывающийся список. Далее ProcessInfo выбирает первый модуль и выводит информацию о нем (рис. 4 В этом режиме утилита ProcessInfo позволяет легко определить, в каких процессах задействован данный модуль. Как видите, полное имя модуля появляется вверх ней части текстового поля, а в разделе Process Information перечисляются все процессы, содержащие этот модуль. Там же показываются идентификаторы и имена процессов, в которые загружен модуль, и его базовые адреса в этих процессах.
Всю эту информацию утилита ProcessInfo получает в основном от различных функций. Чтобы чуточку упростить работу с ToolHelp функциями, я создал+ класс CToolhelp (содержащийся в файле Toolhelp.h). Он инкапсулирует все, что связано с получением моментального снимка состояния системы, и немного облегчает вызов других ToolHelp функций.
Особый интерес представляет функция
GetModulePreferredBaseAddr в файле Pro cessInfo.cpp:
PVOID GetModulePreferredBaseAddr(
DWORD dwProcessId,
PVOID pvModuleRemote);

85
Г ЛАВА 4
Процессы
Рис. 4-5.
ProcessInfo перечисляет все процессы, в адресные пространства которых загружен
модуль User32.dll
Принимая идентификатор процесса и адрес модуля в этом процессе, она просматривает его адресное пространство, находит модуль и считывает информацию из заголовка модуля, чтобы определить, какой базовый адрес для него предпочтителен. Модуль должен всегда загружаться именно поэтому адресу, а иначе приложения, использующие данный модуль, потребуют больше памяти и будут инициализироваться медленнее. Поскольку такая ситуация крайне нежелательна, моя утилита сообщает ослу чаях, когда модуль загружен не по предпочтительному базовому адресу. Впрочем, на эти темы мы поговорим в главе 20 (в разделе Модификация базовых адресов модулей»).
ProcessInfo.cpp
/******************************************************************************
Модуль: Автор Copyright (c) 2000, Джеффри Рихтер (Jeffrey Richter)
******************************************************************************/
#include "..\CmnHdr.h"
/* см. приложение A */
#include
#include
#include
#include
#include
#include "Toolhelp.h"
#include "Resource.h"
Рис. 4-6.
Программа-пример ProcessInfo
см. след. стр.

86
Ч АС Т Ь I I
НАЧИНАЕМ РАБОТАТЬ
Рис. 4-6.
продолжение
///////////////////////////////////////////////////////////////////////////////
// добавляем строку в текстовое поле void AddText(HWND hwnd, PCTSTR pszFormat, ...) {
va_list argList;
va_start(argList, pszFormat);
TCHAR sz[20 * 1024];
Edit_GetText(hwnd, sz, chDIMOF(sz));
_vstprintf(_tcschr(sz, 0), pszFormat, argList);
Edit_SetText(hwnd, sz);
va_end(argList);
}
///////////////////////////////////////////////////////////////////////////////
VOID Dlg_PopulateProcessList(HWND hwnd) {
HWND hwndList = GetDlgItem(hwnd, IDC_PROCESSMODULELIST);
SetWindowRedraw(hwndList, FALSE);
ComboBox_ResetContent(hwndList);
CToolhelp thProcesses(TH32CS_SNAPPROCESS);
PROCESSENTRY32 pe = { sizeof(pe) };
BOOL fOk = thProcesses.ProcessFirst(&pe);
for (; fOk; fOk = thProcesses.ProcessNext(&pe)) {
TCHAR sz[1024];
// помещаем в список имя процесса (без пути) и идентификатор pszExeFile = _tcsrchr(pe.szExeFile, TEXT(''));
if (pszExeFile == NULL) pszExeFile = pe.szExeFile;
else pszExeFile++; // пропускаем наклонную черту ("слэш")
wsprintf(sz, TEXT("%s (0x%08X)"), pszExeFile, pe.th32ProcessID);
int n = ComboBox_AddString(hwndList, sz);
// сопоставляем идентификатор процесса с добавленным элементом, n, pe.th32ProcessID);
}
ComboBox_SetCurSel(hwndList, 0); // выбираем первый элемент имитируем выбор пользователем первого элемента чтобы в текстовом поле появилось что нибудь интересное, IDC_PROCESSMODULELIST,
hwndList, CBN_SELCHANGE, SendMessage);
SetWindowRedraw(hwndList, TRUE);
InvalidateRect(hwndList, NULL, FALSE);
}
///////////////////////////////////////////////////////////////////////////////

87
Г ЛАВА 4
Процессы
Рис. 4-6.
продолжение
VOID Dlg_PopulateModuleList(HWND hwnd) {
HWND hwndModuleHelp = GetDlgItem(hwnd, IDC_MODULEHELP);
ListBox_ResetContent(hwndModuleHelp);
CToolhelp thProcesses(TH32CS_SNAPPROCESS);
PROCESSENTRY32 pe = { sizeof(pe) };
BOOL fOk = thProcesses.ProcessFirst(&pe);
for (; fOk; fOk = thProcesses.ProcessNext(&pe)) {
CToolhelp thModules(TH32CS_SNAPMODULE, pe.th32ProcessID);
MODULEENTRY32 me = { sizeof(me) };
BOOL fOk = thModules.ModuleFirst(&me);
for (; fOk; fOk = thModules.ModuleNext(&me)) {
int n = ListBox_FindStringExact(hwndModuleHelp, 1, me.szExePath);
if (n == LB_ERR) {
// этот модуль еще не был добавлен, me.szExePath);
}
}
}
HWND hwndList = GetDlgItem(hwnd, IDC_PROCESSMODULELIST);
SetWindowRedraw(hwndList, FALSE);
ComboBox_ResetContent(hwndList);
int nNumModules = ListBox_GetCount(hwndModuleHelp);
for (int i = 0; i < nNumModules; i++) {
TCHAR sz[1024];
ListBox_GetText(hwndModuleHelp, i, sz);
// помещаем в список имя модуля (без пути)
int nIndex = ComboBox_AddString(hwndList, _tcsrchr(sz, TEXT('')) + 1);
// сопоставляем индекс полного пути с добавленным элементом
ComboBox_SetItemData(hwndList, nIndex, i);
}
ComboBox_SetCurSel(hwndList, 0); // выбираем первый элемент
// имитируем выбор пользователем первого элемента,
// чтобы в текстовом поле появилось что нибудь интересное
FORWARD_WM_COMMAND(hwnd, IDC_PROCESSMODULELIST,
hwndList, CBN_SELCHANGE, SendMessage);
SetWindowRedraw(hwndList, TRUE);
InvalidateRect(hwndList, NULL, FALSE);
}
///////////////////////////////////////////////////////////////////////////////
PVOID GetModulePreferredBaseAddr(DWORD dwProcessId, PVOID pvModuleRemote) {
PVOID pvModulePreferredBaseAddr = NULL;
см. след. стр.

88
Ч АС Т Ь I I
НАЧИНАЕМ РАБОТАТЬ
Рис. 4-6.
продолжение
IMAGE_DOS_HEADER idh;
IMAGE_NT_HEADERS inth;
// считываем DOS заголовок удаленного модуля, &idh, sizeof(idh), NULL);
// проверяем DOS заголовок его образа if (idh.e_magic == IMAGE_DOS_SIGNATURE) {
// считываем NT заголовок удаленного модуля) pvModuleRemote + idh.e_lfanew, &inth, sizeof(inth), NULL);
// проверяем NT заголовок его образа if (inth.Signature == IMAGE_NT_SIGNATURE) {
// NT заголовок корректен получаем предпочтительный базовый адрес для данного образа pvModulePreferredBaseAddr = (PVOID) inth.OptionalHeader.ImageBase;
}
}
return(pvModulePreferredBaseAddr);
}
///////////////////////////////////////////////////////////////////////////////
VOID ShowProcessInfo(HWND hwnd, DWORD dwProcessID) {
SetWindowText(hwnd, TEXT("")); // очищаем поле вывода th(TH32CS_SNAPALL, dwProcessID);
// показываем подробную информацию о процессе pe = { sizeof(pe) };
BOOL fOk = th.ProcessFirst(&pe);
for (; fOk; fOk = th.ProcessNext(&pe)) {
if (pe.th32ProcessID == dwProcessID) {
AddText(hwnd, TEXT("Filename: %s\r\n"), pe.szExeFile);
AddText(hwnd, TEXT(" PID=%08X, ParentPID=%08X, ")
TEXT("PriorityClass=%d, Threads=%d, Heaps=%d\r\n"),
pe.th32ProcessID, pe.th32ParentProcessID,
pe.pcPriClassBase, pe.cntThreads,
th.HowManyHeaps());
break; // продолжать цикл больше ненужно показываем модули в процессе подсчитываем количество символов для вывода адреса const int cchAddress = sizeof(PVOID) * 2;
AddText(hwnd, TEXT("\r\nModules Information:\r\n")
TEXT(" Usage % *s(% *s) %8s Module\r\n"),
cchAddress, TEXT("BaseAddr"),

89
Г ЛАВА 4
Процессы
Рис. 4-6.
продолжение
cchAddress, TEXT("ImagAddr"), TEXT("Size"));
MODULEENTRY32 me = { sizeof(me) };
fOk = th.ModuleFirst(&me);
for (; fOk; fOk = th.ModuleNext(&me)) {
if (me.ProccntUsage == 65535) {
// модуль загружен неявно, и его нельзя выгрузить, TEXT(" Fixed"));
} else {
AddText(hwnd, TEXT(" %5d"), me.ProccntUsage);
}
PVOID pvPreferredBaseAddr =
GetModulePreferredBaseAddr(pe.th32ProcessID, me.modBaseAddr);
if (me.modBaseAddr == pvPreferredBaseAddr) {
AddText(hwnd, TEXT(" %p %*s %8u %s\r\n"),
me.modBaseAddr, cchAddress, TEXT(""),
me.modBaseSize, me.szExePath);
} else {
AddText(hwnd, TEXT(" %p(%p) %8u %s\r\n"),
me.modBaseAddr, pvPreferredBaseAddr, me.modBaseSize, me.szExePath);
}
}
// показываем потоки в процессе, TEXT("\r\nThread Information:\r\n")
TEXT(" TID Priority\r\n"));
THREADENTRY32 te = { sizeof(te) };
fOk = th.ThreadFirst(&te);
for (; fOk; fOk = th.ThreadNext(&te)) {
if (te.th32OwnerProcessID == dwProcessID) {
int nPriority = te.tpBasePri + te.tpDeltaPri;
if ((te.tpBasePri < 16) && (nPriority > 15)) nPriority = 15;
if ((te.tpBasePri > 15) && (nPriority > 31)) nPriority = 31;
if ((te.tpBasePri < 16) && (nPriority < 1)) nPriority = 1;
if ((te.tpBasePri > 15) && (nPriority < 16)) nPriority = 16;
AddText(hwnd, TEXT(" %08X %2d\r\n"),
te.th32ThreadID, nPriority);
}
}
}
///////////////////////////////////////////////////////////////////////////////
VOID ShowModuleInfo(HWND hwnd, LPCTSTR pszModulePath) {
SetWindowText(hwnd, TEXT("")); // очищаем поле вывода
CToolhelp thProcesses(TH32CS_SNAPPROCESS);
PROCESSENTRY32 pe = { sizeof(pe) };
BOOL fOk = thProcesses.ProcessFirst(&pe);
см. след. стр.
1   ...   8   9   10   11   12   13   14   15   ...   68


База данных защищена авторским правом ©nethash.ru 2019
обратиться к администрации

войти | регистрация
    Главная страница


загрузить материал