Kardinalli писал(а):А почему? Я действительно хочу выбрать оптимальный вариант.
А потому, что DoEvents слишком тяжеловесная функция, и помимо одиночной прокачки очереди сообщений выполняет ряд лишних тяжелых действий, но, что ещё хуже — DoEvents не обеспечивает замораживании потока до прихода ближайшего сообщения, а выполнив тяжеловесные задачи, возвращает управление сразу же. Будучи засунутой в цикл, она обеспечивает загрузку ЦП под 100%.
С идеологической точки зрения это также неверно, потому что один цикл уже есть (под капотом), и нет никакого смысла городить ещё и второй, выполняющийся в рамках итерации внешнего. Кроме того, если понадобится ожидать несколько процессов, в твоём случае это обернётся несколькими циклами, что крайней бессмысленно — должен быть один, и очень хорошо, чтобы это был тот цикл,
который уже есть изначально.
Идеальный вариант, это вариант, когда есть основной поток (GUI-поток), крутящий цикл прокачки сообщений (как обычно), и есть ждущий поток, который сигналит первому при помощи сообщений. В моём случае вместо сигнализации сообщениями используется флаговый подход. Авторы Wscript.Shell непонятно чем думали: могли бы сделать событие изменения статуса процесса, на которое можно подписаться.
Kardinalli писал(а):Работает без проблем, закрывает. Нет ли подводных камней?
Ну да, работает, но я бы так делать не стал бы (если бы писал что-то отличное от временной утилитки), а использовал бы соответствующую API-функцию. Подводные камни, конечно же, есть. И не важно, на сколько маловероятно то, что я сейчас опишу, ибо по сравнению с вероятностью, что что-то не так будет с API-функцией, эти вероятности выглядят огромными.
Во-первых, набор API-функцией — более-менее солидная вещь. Не может API-функция просто так внезапно исчезнуть, их набор поддерживают в работоспособном виде для разработчиков, а если какую-то API-функцию хотят упразднить — заранее уведомляют об этом в соответствующей странице документации для разработчиков. API-функции позиционируются как «вещь для разработчиков», и соответствующие люди над этим трясутся. Набор утилит не позиционируется как инструментарий для разработчиков — скорее как инструмент для администрирования. Что-то я не припомню, чтобы где-то MS задокументировали (на правах документа для разработчиков, а не хелпа для юзеров, относящегося к конкретной версии продукта), что есть такой-то набор утилит, что они обещают сохранять по возможности неизменным, что смысл ключиков командной строки гарантированно не поменяется в какой-то следующей версией. Поэтому API-функции намного лучше.
Во-вторых, помимо ситуации, когда
taskkill отсутствует, потому что его убрали из штатной поставки ОС вообще, может оказаться, что
taskkill отсутствует на конкретной машине. Это может случиться по разным причинам: какая-либо кастомная сборка ОС, например минималистическая, которая загружается с CD, либо потому что кто-то (что-то) удалил эту программу непреднамеренно, а SFP отключён, либо потому, что её удалили намеренно (не важно, в силу каких причин). С API-функцией такого произойти не может.
В-третьих, этот способ не сработает, если в папке с программой окажется другой
taskkill. В этом случае будет вызван не системный
taskkill, а тот, который лежит рядом (который может либо делать что-то совершенно другое, либо вообще не работать). Конечно, если программа живёт в своём собственном каталоге, и устанавливается в Program Files, вероятность такого случая — мала (но остаётся), но есть такой популярный формат распространения программ как «portable», когда программа не требует установки, когда она может жить в любой папке, когда она распространяется в виде одного EXE-файла, не зависящего от каких-либо рядом лежящих файлов. В этом случае вероятность такого отказа сильно увеличивается: если твой EXE-файл может лежать где угодно (и при этом будет работать), может запросто оказаться, что рядом лежит какой-то левый
taskkill. Например, я часто прошу мне скинуть какую-нибудь программку из другой версии ОС, чтобы что-то проверить/поисследовать. И вот будет у меня лежать на рабочем столе taskkill из 64-битной Win7, и кину я на рабочий стол твой exe-шник, и случится облом. А с API-функций такие сценарии исключены.
В-четвёртвых, очень легко может быть, что твоё программа будет запущено в изменённом окружении или сама изменит своё окружение. Если более конкретно, то речь идёт о переменных окружения. Если ещё более конкретно — о переменной окружения
PATH. Именно эта переменная окружения используется, когда запускается процесс, полный путь к которому не указан. Windows будет искать исполняемый файл в местах, перечисленных в переменной окружения
PATH. Если модифицировать значение этой переменной, твой запуск
taskkill обломается — система не сможет его найти.
Вот я в командной строке вызываю
taskkill (он вызывается и выводит свой краткий хелп), затем порчу
PATH и пытаюсь второй раз вызвать
taskkill — система уже не может его найти:
- running_taskkill_with_spoiled_path_envvar.png (8.8 Кб) Просмотров: 3845
Если из этого окна командной строки запустить твою программу, она таким же образом не сможет запустить
taskkill. И, естественно, испортить переменную окружения
PATH можно не только из командной строки: это может сделать любой процесс, который запустит твой процесс, или это может сделать сам же твой процесс, или это может быть сделано глобально на уровне пользователя ОС или на уровне всей ОС. А с API-функцией подобные сценарии отменяются в принципе.
В-пятых, запуск
taskkill — это всё таки создание (порождение) нового процесса. Ситуация, когда для завершения одного процесса нужно сначала породить новый другой, попахивает дебилизмом. В условиях недостатка ресурсов (например памяти ядра или свободных страниц в файле подкачки) может случиться так, что запуск нового процесса обломится. И мы получим ситуацию, когда из-за нехватки памяти мы не можем уничтожить процесс, хотя как раз убийство процесса освободило бы память и сильно улучшило бы положение дел. Это всё равно, что умереть от жажды, потому что не было сил открыть рот (а не потому, что не было воды). И такая ситуация (когда не хватает системных ресурсов на запуск
taskkill) вовсе не надуманная и не такая нереалистичная, как могло бы показаться, — я буквально месяц назад с ней сталкивался. Я рендерил видео к
этому посту в Sony Vegas-е, и он никак не мог его отрендерить — говорил «недостаточно памяти». При том, что места в файлах подкачки было явно навалом, было понятно, что не хватает не памяти как таковой, а свободного региона в адресном пространстве. Погуглив по точной строке с сообщением об ошибке, я наткнулся на то, что люди решают эту проблему, добавляя EXE-файлу атрибут
LargeAddressAware (с помощью editbin). Но мне-то было совершенно очевидно, что одно лишь добавление этого атрибута вообще никак не влияет на ситуацию: наличие этого атрибута сыграет свою роль только в том случае, если система была загружена с ключом
/3GB в boot.ini. С этим ключом ядерная часть АП всех процессов будет ужата до 1 GB, а пользовательская часть — расширена до 3 GB. Впрочем, без этого атрибута система всё равно будет выделять процессу память только из нижних двух гигабайт, и только с наличием атрибута
LargeAddressAware процесс сможет воспользоваться 3-х гигабайтным АП.
Поэтому я выдал Vegas-у этот атрибут, а в boot.ini добавил ключ
/3GB. Это имело вполне ожидаемый исход: система вообще не могла загрузиться. Ключ
/3GB заставлял ядерную часть АП стиснутся с 2 Гб до 1 Гб (то есть уменьшиться вдвое), а у меня в то же самое время был включен режим PAE, включение которого сильно увеличивает размер таблиц PDE/PTE (размер структур PTE и PDE без PAE — 4 байта, в режиме PAE — в два раза больше — 8 байт). Режим PAE был задействован из-за того, что был включен аппаратный DEP. Поэтому я отредактировал boot.ini так, чтобы запуститься с
/3GB но без DEP (а значит без использования PAE).
Загрузиться смог, но сильно это не помогло: большая часть драйверов не смогла загрузиться, работал только основной монитор, да и то в режиме 640×480 · 16 цветов, но что самое главное: я не могу запустить практически ничего, потому что ядру не хватало памяти. Было ясно, что о Вегасе не приходится и мечтать, и нужно было хотя бы вернуть boot.ini в исходное состояние. Но не тут-то было: не хватало ресурсов, чтобы запустить тот же Notepad. Нужно было позавершать разные фоновые процессы, но не хватало ресурсов даже на то, чтобы открыть Диспетчер задач по Ctrl+Alt+Del. Удавалось открыть
cmd, но и оттуда taskkill не мог нормальо запуститься — не хватило памяти. Тем не менее, я тогда как-то выкрутился и сумел убить explorer, поубивать taskkill-ом все фоновые процессы, запустить блокнот и отремонтировать boot.ini. А видео в итоге пришлось 5 часов рендерить на виртуальной машине (которая легко загрузилась с ключом
/3GB).
В-шестых, запуск
taskkill может быть попросту запрещён административными настройками на целевой машине.
В-седьмых, уничтожение процесса с помощью так или иначе выполняется с помощью API-функции
TerminateProcess — не важно, кто будет ею пользоваться (твоё приложение само или вызванный им
taskkill). Этой функции нужен хендл процесса. Но не просто хендл, а хендл с правом на уничтожение процесса. При этом порождающий процесс при создании дочернего может получить хендл с полным набором прав. Сторонний же процесс (который не порождал убиваемый) вынужден использовать
OpenProcess, чтобы из PID-а получить хендл. Есть вероятность, что при запросе хендла по PID-у, из-за особенностей административной настройки конкретной машины, процесс taskkill не сможет получить хендл с правом на уничтожение процесса жертвы, а значит и не сможет его убить. При этом процесс-родитель такой хендл может иметь запросто.
Ну и в конце-концов, то есть в-восьмых: есть некоторая комбинация 3-го и 4-го пунктов, которая выглядит даже более реалистичной, чем эти пункты в отдельности. Не обязательно, чтобы левый
taskkill лежал в папке с нашей программой. Не обязательно портить переменную окружения
PATH. Она может быть вполне законно изменена после установки какого-то софта так, что будет включать какую-то директорию, в которой будет лежать какая-то левая
taskkill какого-то стороннего софта. Например у меня так после установки
cygwin-а в
PATH добавился каталог с cygwin-овскими бинарниками (ls, cat, grep, ps), при этом cygwin-овский бинарник
link (создающий ссылки уровня файловой системы) перекрыл Microsoft-овский линкер
link (из состава Visual Studio или Platform SDK), а cygwin-овский бинарник
find перекрыл системный (штатный) бинарник
find. Cygwin делали отнюдь не дураки, и, тем не менее, мы получили такой конфликт имён. А где гарантия, что какой-нибудь софт не притащит свой taskkill, убивающий какую-то свою задачу в терминах этого софта, на компьютер и не занесёт каталог, в котором эта программа лежит, в
PATH? А с использованием API-функции такой сценарий исключён.