понедельник, 15 августа 2011 г.

Помните, что происходит, когда вы делаете широковещательную рассылку

Это перевод Remember what happens when you broadcast a message. Автор: Реймонд Чен.

Иногда я вижу, как люди делают вещи вроде широковещательной рассылки (broadcasting) сообщения WM_COMMAND всем окнам верхнего уровня (top-level windows). Это одна из вещей, которые настолько очевидно неправильные, что я не понимаю, как кому-то вообще может приходить в голову идея, что это будет работать.

Предположим, вы делаете такую рассылку:
SendMessage(HWND_BROADCAST, WM_COMMAND, 100, 0);

Что произойдёт?

Каждое окно верхнего уровня получает сообщение с одними и теми же параметрами, и каждое окно верхнего уровня начинает интерпретировать эти параметры на своё собственное усмотрение. Как вы знаете (поскольку вы же писали подобное), каждая оконная процедура определяет свои собственные элементы меню и дочерние окна, так что нет гарантий, что команда 100 будет делать одну и ту же вещь в каждом окне. Диалоговое окно с таким шаблоном:
#define IDC_USEDEFAULT 100
...
    AUTORADIOBUTTON "Use &default color",
                    IDC_USEDEFAULT, 14, 38, 68, 10, WS_TABSTOP
проинтерпретирует это сообщение как:
id 	= 	IDC_USEDEFAULT (100)
command 	= 	BN_CLICKED (0)
window 	= 	NULL (0) — недопустимый параметр
В зависимости от того, как написана оконная процедура, она может попытаться отправить сообщение кнопке (ей это не удастся, поскольку вы передали 0 в качестве оконного описателя), или она может обновить состояние диалога вроде отключения элементов управления (поскольку ей сказали, что пользователь щёлкнул на кнопку "User default color").

Какой-то другой диалог может иметь такой шаблон:
#define IDC_CHANGE 100
...
    PUSHBUTTON      "C&hange", IDC_CHANGE, 88, 95, 50, 14
Его диалоговая процедура проинтерпретирует сообщение как:
id 	= 	IDC_CHANGE (100)
command 	= 	BN_CLICKED (0)
window 	= 	NULL (0) — недопустимый параметр
Реакцией, вероятно, будет применение текущих изменений в диалоге.

А в это время ещё какое-то окно имеет меню, заданное так:
#define IDC_REFRESH 100
...
        MENUITEM "&Refresh", IDC_REFRESH
Оно проинтерпретирует сообщение как выбор пользователем элемента меню "Refresh".
id 	= 	IDC_REFRESH (100)
command 	= 	0 — недопустимый параметр, должно быть равно 1 для элементов меню
window 	= 	NULL (0)
Это не только будет являться не корректной командой для меню, но к тому же окно может находится в состоянии, когда программа отключила пункт "Refresh". И тем не менее вы отправили ей сообщение, как если бы пользователь выбрал пункт меню, что невозможно. Поздравляю, вы только что поставили программу в невозможную ситуацию, и она вполне может вылететь в результате этих действий. К примеру, программа могла заблокировать пункт меню потому что у неё нет объектов для обновления. Когда вы отправите ей команду обновления, она попытается обновить несуществующий объект и вылетит с разыменованием nil-указателя.

Очевидно, вы не можете делать широковещательную рассылку сообщения WM_COMMAND, потому что не существует его универсальной интерпретации в программах. Сообщение, которое означает команду "Обновить" для одного окна, вполне может означать команду "Изменить" для другого окна.

Та же логика применима почти ко всем стандартным сообщениям Windows. Вот сообщения, которые специально разработаны для широковещательной рассылки: Если вы попытаетесь сделать широковещательную рассылку сообщения в диапазоне от WM_USER до WM_APP, то вы ещё безумнее, чем я думал. Как мы уже видели, смысл оконных сообщений в этом диапазоне определяется оконным классом или приложением, которое создало окно. Здесь уже контекстно-чувствительны не только параметры сообщения, но и само сообщение! Это означает, что отправка произвольному окну сообщения (к примеру) WM_USER + 1 приведёт к полностью случайному поведению системы (мы видели это ранее в контексте широковещательной рассылки, но это так же работает и для прямой доставки сообщения). Если это диалоговое окно, оно решит, что вы отправили ему сообщение DM_SETDEFID, и в итоге вы смените действие по умолчанию в диалоге. Если это общий диалог (common dialog), он решит, что это сообщение WM_CHOOSEFONT_GETLOGFONT, и если вам повезёт, то он вылетит при попытке доступа к TLogFont по недопустимому указателю (если вам НЕ повезёт, то параметры, которые вы передали с сообщением, позволят коду выполнится, приводя к работе кода программы над случайными данными, приводя к их порче и странному поведению программы). Если это элемент управления tooltip, то вы отправили ему TTM_ACTIVATE и, значит, что-то сделали с его состоянием активации.

Эти же предупреждения с использованием той же логики применимы и к отправке сообщений без универсального смысла окнам, с чьим оконным классом у вас нет интерфейсного контракта. К примеру, я видел как программисты отправляют сообщение PSM_PRESSBUTTON окну, которое, как они полагают без всяких на то оснований, является вкладкой свойств (property sheet).

Помните про это, когда вы собираетесь отправить сообщение в окно или окна: вам нужно быть уверенными, что окно (или окна) проинтерпретируют отправленное вами сообщение именно ожидаемым вами способом.

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

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

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

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

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

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

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