На главную страницу


View in English


Как корректно завершить работу системы

При разработке управляющего программного обеспечения часто возникает необходимость в коде, завершающем работу операционной системы без участия пользователя. Казалось бы, что может быть проще: имеется функция API ExitWindowsEx, делающая это, однако столь простое решение работает далеко не всегда. Дело в том, что каждое приложение, запущенное на компьютере, имеет право отменить завершение работы системы или приостановить его, показав всплывающее окно. Чтобы этого не происходило, в принципе при вызове ExitWindowsEx можно применять флаг EWX_FORCE, но в этом случае все приложения завершаются методом "грубой силы", что чревато потерей данных. Некоторые приложения на такой способ завершения работы реагируют неадекватно, например, WinPopup выводит всплывающее окно запроса, а если в момент вызова ExitWindowsEx на экране уже были какие-либо всплывающие окна, может произойти крах системы.

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

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

Итак, действие первое: закрываем все имеющиеся на экране всплывающие окна, посылая им сообщения WM_CLOSE. Ждем некоторое время, пока приложения обработают сообщения.

Действие второе: посылаем во все главные окна приложений сообщение WM_CLOSE. Это эквивалентно нажатию на крестик в правом верхнем углу окна. Разные приложения на это реагируют по-разному: одни безропотно закрываются, другие выводят всплывающее окно, третьи перемещаются в трей, четвертые просто игнорируют это сообщение. Опять ждем некоторое время.

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

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

Действие пятое: снова закрываем всплывшие окна. Ждем.

Действие шестое: оставшиеся приложения уничтожаем при помощи API TerminateProcess. Мы их предупреждали! Тут следует иметь в виду, что несколько главных окон приложений могут принадлежать одному процессу, и поэтому нужно принять меры к тому, чтобы функция TerminateProcess к таким процессам не применялась многократно. Еще раз ждем.

Действие седьмое: вот теперь, наконец, можно вызвать функцию ExitWimdowsEx.

В вышеприведенной последовательности действий приложения, не имеющие видимых окон, не закрываются до вызова ExitWimdowsEx. Как правило, такие приложения специально разработаны для работы без вмешательства пользователя, и нормально завершают работу вместе с операционной системой.

Для ряда приложений предпочтительнее, чтобы они завершались только вместе с ОС: например, Проводник и Internet Explorer в этом случае сохраняют состав и расположение окон. В эту группу входит также Диспетчер Задач и WinPopup. Классы их окон составляют список исключений при посылке сообщений.

В Windows NT/2000 имеются свои особенности применения всего изложенного выше. Во-первых, ExitWindowsEx будет правильно работать только в том случае, если процесс запущен под учетным именем с достаточными правами (оно должно принадлежать группе Administrators или Server Operators), но этого мало: процессу нужно еще дать привилегию "SeShutdownPrivilege". Как это сделать - см. исходный код и статью из Knowledge Base. Во-вторых, все это имеет смысл только тогда, когда программа, завершающая работу системы, работает на интерактивном рабочем столе. Если эта программа запущена как служба или через Task Scheduler, она может "не увидеть" окон пользовательских приложений, и не сможет их закрыть. Впрочем, в Windows NT/2000 имеется функция InitiateSystemShutdown, которая практически всегда завершает работу системы.

См. также:

Microsoft Knowledge Base Q161136, Q168796.
Загрузить исходный код
shsystem.zip (18 кБ)

Эта страница обновлялась в последний раз 13 июн 2000 г.
© 2000 Сергей Мерзликин
Пишите: