Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Создание эффективных приложений для Windows Джеффри Рихтер 2004 (Книга).pdf
Скачиваний:
375
Добавлен:
15.06.2014
Размер:
8.44 Mб
Скачать

ее элемент th32ParentProcessID возвращает идентификатор "родителя" данного процесса. Тем не менее, если Вашей программе нужно взаимодей

ствовать с родительским процессом, от идентификаторов лучше отказаться. Почему — я уже говорил. Для определения родительского процесса существуют более надежные механизмы: объекты ядра, описатели окон и т. д.

Единственный способ добиться того, чтобы идентификатор процесса или потока не использовался повторно, — не допускать разрушения объекта ядра "процесс" или "поток". Если Вы только что создали новый процесс или поток, то можете просто не закрывать описатели на зти объекты — вот и все. А по окончании операций с иден тификатором, вызовите функцию CloseHandle и освободите соответствующие объек ты ядра. Однако для дочернего процесса этот способ не годится, если только он не унаследовал описатели объектов ядра от родительского процесса.

Завершение процесса

Процесс можно завершить четырьмя способами:

входная функция первичного потока возвращает управление (рекомендуемый способ),

один из потоков процесса вызывает функцию ExitProcess (нежелательный спо соб); поток другого процесса вызывает функцию TerminateProcess (тоже нежелательно); все потоки процесса умирают по своей воле (большая редкость),

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

Возврат управления входной функцией первичного потока

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

любые С++-объекты, созданные данным потоком, уничтожаются соответству ющими деструкторами; система освобождает память, которую занимал стек потока-,

система устанавливает код завершения процесса (поддерживаемый объектом ядра "процесс") — его и возвращает Ваша входная функция; счетчик пользователей данного объекта ядра "процесс" уменьшается на 1.

Функция ExitProcess

Процесс завершается, когда один из его потоков вызывает ExitProcess:

VOID ExilProcess(UINT fuExitCode);

Эта функция завершает процесс и заносит в параметр fuExitCode код завершения процесса. Возвращаемого значения у ExitProcess нет, так как результат ее действия —

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

Когда входная функция (WinMain, wWinMain, main или wmairi) в Вашей програм ме возвращает управление, оно передастся стартовому коду из библиотеки С/C++, и тот проводит очистку всех ресурсов, выделенных им процессу, а затем обращается к

ExitProcess, передавая ей значение, возвращенное входной функцией Вот почему воз врат управления входной функцией первичного потока приводит к завершению все го процесса. Обратите внимание, что при завершении процесса прекращается выпол нение и всех других его потоков.

Кстати, в документации из Platform SDK утверждается, что процесс не завершает ся до тех пор, пока не завершится выполнение всех его потоков. Это, конечно, верно, но тут есть одна тонкость. Стартовый код ил библиотеки С/С++ обеспечивает завер шение процесса, вызывая ExitProcess после того, как первичный поток Вашего прило жения возвращается из входной функции. Однако, вызвав из нее функцию ExitThread (вместо того чтобы вызвать ExitProcess или просто вернуть управление), Вы заверши те первичный поток, но не сам процесс — если в нем еще выполняется какой-то дру гой поток (или потоки).

Заметьте, что такой вызов ExitProcess или ExitTbread приводит к уничтожению процесса или потока, ко1да выполнение функции еще не завершилось. Что касается операционной системы, то здесь все в порядке: она корректно очистит все ресурсы, выделенные процессу или потоку Но в приложении, написанном на С/С++, следует избегать вызова этих функций, так как библиотеке С/С++ скорее всего нс удастся провести должную очистку. Взгляните на этот код:

#include <windows n> #include <stdio h>

class CSomeObj { public:

CSomeOtrK) { printf("Constructor\r\n"), } ~CSomeObj() { printf("Destructor\r\n"); } };

CSomeObj g_GlobalObj;

void main () { CSomeObj LocalObj;

ExitProcess(0); // этого здесь не должно быть

// в конце этой функции компилятор автоматически вставил код // дли вызова деструктора LocalObj, но ExitProcess не дает его выполнить }

При его выполнении Вы увидите:

Constructor

Constructor

Код конструирует два объекта: глобальный и локальный Но Вы никогда не увиди те строку Destructor С++-объекты не разрушаются должным образом из-за того, что ExitProcess форсирует уничтожение процесса и библиотека С/С++ не получает шанса на очистку.

Как я уже говорил, никогда не вызывайте ExitProcess в явном виде. Если я уберу из предыдущего примера вызов ExttProcess, программа выведет такие строки:

Constructor

Constructor

Destructor

Destructor

Простой возврат управления от входной функции первичного потока позволил библиотеке С/С++ провести нужную очистку и корректно разрушить С++-объекты. Кстати, все, о чем я рассказал, относится не только к объектам, но и ко многим дру гим вещам, которые библиотека С/С++ делает для Вашего процесса.

NOTE

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

Функция TerminateProcess

Вызов функции TerminateProcess тоже завершает процесс:

BOOL TerminateProcess( HANDLE hProcoss, UINT fuExitCode);

Главное отличие этой функции от ExitProcess в том, что ее может вызвать любой поток и завершить любой процесс. Параметр bProcess идентифицирует описатель завершаемого процесса, а в параметре fuExitCode возвращается код завершения про цесса.

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

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

По завершении процесса (не важно каким способом) система гарантирует: после него ничего не останется — даже намеков на то, что он когда-то выполнялся. Завер шенный процесс не оставляет за собой никаких следов. Надеюсь, я сказал ясно.

NOTE

TerminateProcess — функция асинхронная, т. e. она сообщает системе, что Вы хотите завершить процесс, но к тому времени, когда она вернет управление, процесс может быть еще не уничтожен. Так что, если Вам нужно точно знать момент завершения процесса, используйте WaitForSingleObject (см. главу 9) или аналогичную функцию, передав ей описатель этого процесса.

Когда все потоки процесса уходят