вторник, 16 августа 2011 г.

Блуждая в темноте и спотыкаясь о неверное решение

Это перевод Fumbling around in the dark and stumbling across the wrong solution. Автор: Реймонд Чен.

Я не хотел придираться к этой серии записей, но она иллюстрирует интересный шаблон спотыкания на неверном "решении".

Эта серия пытается запустить системный триггер неактивности монитора отправкой сообщения окну рабочего стола (desktop window).

Как мы видели ранее, окно рабочего стола является очень особенным окном, и его, как правило, нужно избегать, поскольку оно не ведёт себя так же, как обычные окна приложений. В частности, в коде поста автор пытается отправить (post) сообщение окну рабочего стола. Это работало раньше в исторически открытом мире оконного менеджера, но требования безопасности и надёржности взяли приоритет над обратной совместимостью. В Windows XP SP2 окно рабочего стола не чувствительно к отключению, потому что программы продолжали неосознанно делать это, и оно блокирует отправленные ему сообщения. Я думаю, что это было сделано для противодействия shatter атакам. Да, это улучшило надёжность и стабильность, но также это сломало хак с PostMessage, на который рассчитывал код.

Начинается раунд 3, где автор ищет другие окна, которым можно было бы отправить сообщение, и в итоге он находит загадочное окно, которое, кажется, работает: HWND_TOPMOST = -1.

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

(Аналогично, я видел, как люди задают вопросы вроде "Что означает сообщение 49251?" Это обратный случай: люди увидели какое-то число и пытаются присвоить ему смысл. Номера сообщений, начинающиеся с $C000 (десятичное - 49152), являются сообщениями, регистрируемыми через RegisterWindowMessage. Числовое значение сообщения, ассоциированного с зарегистрированным сообщением, меняется от запуска к запуску, его нельзя предсказать. Единственная гарантия - это постоянство в рамках одного рабочего стола).

Если вы посмотрите более внимательно на итоговый код, вы увидите, что "решение", на самом деле, является ещё одним багом. Так получилось, что значение -1 оконного описателя подозрительно похоже на значение HWND_BROADCAST:
const
  HWND_BROADCAST = $FFFF;
Внутренне оконный менеджер принимает -1 (что есть $FFFFFFFF) за HWND_BROADCAST (объяснить, почему это так, - я оставляю вам в качестве упражнения; мы можете это сделать на основе наших предыдущих обсуждений). В результате, на самом деле, автор делает широковещательную рассылку сообщения всем окнам верхнего уровня! Как мы не раз видели ранее, широковещательная рассылка - это довольно опасное занятие, но в этом случае автору повезло в том, что все окна интерпретируют это сообщение одинаково - способом, который допускает многократную обработку сообщения, и ни одно окно не выполняет никакой особой фильтрации для этого сообщения (ещё один автор набрёл на это же неверное "решение", но не предоставил никаких деталей о процессе, в результате которого он пришёл к этому решению. И ещё один автор обнаружил некоторые проблемы, но не сложил все кусочки вместе).

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

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

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.