    РАСПАКОВЩИКИ СООБЩЕНИЙ 

    приложение   B 

    Когда Windows только-только появилась, было всего два языка 
программирования, пригодных для разработки приложений под эту операционную 
систему: С и ассемблер. Причем из всех компиляторов С только компилятор 
Microsoft позволял создавать исполняемые файлы — других не было. Но за 
последние несколько лет все изменилось. Теперь Вы можете разрабатывать 
Windows-приложения, используя такие языки, как Ада, ассемблер, С, C++, COBOL, 
FORTRAN, Java, LISP, Modula-2, Паскаль, REXX и Smalltalk/V. И, конечно, не 
забудьте про Бейсик. 
    Но даже с появлением поддержки Windows в этих языках чаще все-таки 
применяют С; при этом потихоньку растет популярность C++. Когда мне пришлось 
выбирать язык для написания программ-примеров в книге, я сузил круг выбора до 
такого списка: 
    1. Стандартный С. 
    2. С плюс распаковщики сообщений (message crackers). 
    3. Стандартный C++. 
    4. C++ с использованием библиотеки MFC (Microsoft Foundation Classes). 
    Принять решение оказалось непросто. 
    Мне хотелось, чтобы книга привлекла как можно более широкую аудиторию, 
поэтому я сразу отверг пункт 4. Не стойло применять какую-то конкретную 
библиотеку классов — такие библиотеки выпускаются несколькими компаниями; да и 
незачем вводить в примеры посторонний код. (А если уж и выбирать, то лично мне 
больше нравится библиотека MFC; в крупных проектах я использую именно ее.) 
    Пункт 3 мне тоже не показался многообещающим. Разрабатывать программы для 
Windows на C++ без библиотеки классов даже легче, чем на стандартном С, но, 
поскольку примеры в этой книге сравнительно невелики, применение C++ не дало 
бы особого выигрыша, И еще одна важная причина, по которой я отверг пп. 3 и 4, 
— большинство програмистов, создавая Windows-приложения, по-прежнему верны С и 
еще не перешли на C++. 
    Пункт 1 вроде вполне подходящий: тем, кто стал бы разбираться в моих 
программах, не понадобилось бы изучать какие-то дополнительные вещи. И 
все-таки я остановился на пункте 2. На конференциях я часто спрашиваю: 
«Применяете ли Вы распаковщики сообщений?» — и обычно получаю один ответ — 
нет. Оказывается, часто даже не знают, что такое распаковщики сообщений и для 
чего они служат. Используя в примерах язык С совместно с распаковщиками 
сообщений, я стремился показать всем эти малоизвестные, но очень полезные макросы. 
    Распаковщики содержатся в файле WINDOWSX.H, поставляемом с Visual C++. 
Этот файл обычно включается сразу после WINDOWS.Н, и он — не что иное, как 
набор опера

    658 _____________________________Windows для профессионалов 

    торов #define, определяющих группу макросов. Microsoft создала эти 
макросы, чтобы предоставить разработчикам ряд дополнительных возможностей: 
    • Сократить число явных преобразований типов в коде программы, а также 
возникающих при этом ошибок. Большой объем преобразований типов — одна из 
насущных проблем в программировании для Windows на С. Редко, когда вызов 
\Уш32-функции не требует такого преобразования. В то же время явных 
преобразований типов следует избегать — они препятствуют выявлению возможных 
ошибок при компиляции. Явно преобразуя типы, Вы говорите компилятору: 
    «Здесь передается неправильный тип, но это нормально. Мне лучше знать, что 
надо, а что нет.» Однако при большом количестве таких преобразований ошибиться 
очень легко. А потому позвольте компилятору максимально помочь Вам в выявлении 
ошибок. Если Вы применяете распаковщики сообщений, число явных преобразований 
типов резко уменьшается. 
    • Сделать код более читабельным. 
    • Упростить перенос 16-разрядных Windows-приложений в Win32. 
    • Распаковщики сообщений просты и понятны — в конце концов это всего лишь макросы. 
    • Их легко включить в уже написанную программу. Переделка всего 
существующего кода не потребуется. 
    • Их можно применять в коде, написанном как на С, так и на C++, хотя в них 
нет нужды при работе с библиотекой классов. 
    • Если Вам понадобится что-то, чего макросы дать не могут, Вы сами 
напишете то, что нужно, следуя модели, используемой в заголовочном файле. 
    • Работая с этими макросами, Вам не придется обращаться к справочной 
информации или запоминать туманные конструкции Windows. Например, многие 
функции в Windows принимают параметр типа LONG, в младшем слове которого 
содержится одна величина, а в старшем — совсем другая. Перед вызовом функции 
из двух этих значений надо собрать одно (типа LONG). Обычно это делается с 
помощью макроса MAKELONG, определенного в файле WINDEF.H. Сколько раз я 
ошибался при его использовании — не сосчитать, а ведь в результате функция 
получала неверный параметр. И здесь тоже помогут макросы из WINDOWSX.H. 
    Макросы из файла WINDOWSX.H можно разбить на 3 группы: распаковщики 
сообщений, макросы для работы с дочерними элементами управления и API-макросы. 

    Макросы — распаковщики сообщений 

    Распаковщики сообщений упрощают написание оконных процедур. Последние 
обычно представляют собой один огромный оператор switch. Мне случалось видеть 
в оконных процедурах операторы switch, содержавшие свыше 500 строк кода. Все 
мы знаем, что это образец плохого стиля, но... продолжаем писать именно так. Я 
и сам этим грешу. А распаковщики заставляют разбивать оператор switch на 
небольшие функции — по одной на оконное сообщение. Это значительно улучшает 
внутреннюю структуру кода. 
    Другая проблема с оконными процедурами в том, что у каждого сообщения есть 
параметры wParam и IParam, смысл которых зависит от типа сообщения. Бывает, 
что в сообщении WM_COMMAND параметр wParam содержит два разных значения. 
Старшее его слово — код уведомления, младшее — идентификатор элемента 
управления. Или наоборот? Вечно я это путаю. Хуже того, в 16-разрядной Windows 
параметр IParam сообщения 

    ____________________ Приложение Б. Распаковщики сообщений 659 

    WM_COMMAND содержит описатель окна и код уведомления. Но если Вы 
используете распаковщики, Вам не придется запоминать эту информацию или искать 
ее в справочниках. Такие макросы названы распаковщиками потому, что они 
распаковывают параметры заданного сообщения. Чтобы обработать WM_COMMAND, Вы 
просто пишете функцию, которая выглядит примерно так: 

    void Cls_OnCommand(HWND hwnd, int id, HWND hwndCtl. UINT codeNotify) { 
    switch (id) { 
    case ID_SOMELISTBOX: 
    if (codeNotify != LBN_SELCHANGE) 
    break; 
    
    // обрабатываем LBN_SELCHANGE 
    break: 
    case ID_SOMEBUTTON: 
    break: 

    Смотрите, как просто! Распаковщики берут параметры wParam и IParam, 
расщепляют их на отдельные компоненты и вызывают Вашу функцию. Файл WINDOWSX.H 
существует в 2 версиях: для Win32 и для 16-разрядной Windows. Распаковщик для 
16-разрядной Windows работает иначе, чем распаковщик для Win32. Но неважно, 
как там происходит распаковка, — Вы по-прежнему пишете только одну функцию и 
мгновенно получаете код, корректно компилируемый и правильно работающий как в 
16-разрядной Windows, так и в Win32! 
    Чтобы использовать распаковщики, нужно внести кое-какие изменения в 
оператор switch оконной процедуры: 

    LRESULT WndProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM IParam) { 
    switch (uMsg) { 
    HANDLE_MSG(hwnd, WM_COMMAND, Cls_OnCommand); 
    HANDLE_MSG(hwnd, WM_PAINT, Cls_OnPaint); 
    HANDLE_MSG(hwnd, WM_DESTROY, Cls_OnDestroy); 
    default: 
    return(DefWindowProc(hwnd, uMsg, wParam, IParam)); 
    } 
    } 

    В файле WINDOWSX.H макрос HANDLE_MSG (и для 16-разрядной Windows, и для 
Win32) определен так: 

    ttdefine HANDLE_MSG(hwnd. message, fn) \ 
    case (message): \ 
    return HANDLE_##message((hwnd), (wParam), (IParam), (fn)); 

    Для сообщения WM_COMMAND эта строка после обработки препроцессором 
выглядит как: 

    case (WM_COMMAND): 
    return HANDLE_WM_COMMAND((hwnd), (wParam), (IParam), (Cls_OnCommand)); 


    660 _____________________________Windows для профессионалов 

    Макросы HANDLE_WM_* определены тоже в WINDOWSX.H. Они-то и есть настоящие 
распаковщики сообщений. Они распаковывают содержимое параметров wParam и 
IParam, выполняют нужные преобразования типов и вызывают соответствующую 
функцию — обработчик сообщения (вроде показанной ранее функции Cls_OnCommand}. 
Версия макроса HANDLE_WM_COMMAND для 16-разрядной Windows: 

    #define HANDLE_WM_COMMAND(hwnd, wParam, IParam, fn) \ 
    ((fn)((hwnd), (int)(wParam), (HWND)LOWORD(lParam), 
    (UINT)HIWORD(lParam)), OL) 

    а для Win32: 

    fldefine HANOLE_WM_COMMAND(hwnd, wParam, IParam, fn) \ 
    ((fn)((hwnd), (int)(LOWORD(wParam)), (HWND) (IParam), 
    (UINT)HIWORD(wParam)), OL) 

    Результат раскрытия препроцессором любого из этих макросов — вызов функции 
Cls_OnCommand, которой передаются (после соответствующих преобразований типов) 
распакованные части параметров wParam и IParam. 
    Чтобы использовать распаковщик для обработки сообщения, откройте файл 
WINDOWSX.H и найдите сообщение, которое Вы собираетесь обрабатывать. Например, 
если Вас интересует сообщение WM_COMMAND, ищите фрагмент файла, содержащий строки: 

    /* void Cls_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify); */ 
    ftdefine HANDLE_WM_COMMAND(hwhd, wParam, IParam, fn) \ 
    ((fn)((nwnd), (int)(LOWORD(wParam)), (HWND)(lParam), 
    (UINT)HIWORD(wParam)), OL) 
    Bdefine FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, fn) \ 
    (void)(fn)((hwnd), WM_COMMAND, 
    MAKEWPARAM((UINT)(id), (UINT)(codeNotify)), 
    (LPARAM)(HWND)(hwhdCtl)) 

    Первая строка — комментарий, показывающий прототип функции, которую Вы 
должны написать. Этот прототип одинаков и для 16-разрядной Windows, и для 
Win32. Следующая строка — уже рассмотренный нами макрос HANDLE_WM_*, а 
последняя содержит предописатель сообщения (message forwarder). Допустим, при 
обработке сообщения WM_COMMAND Вы хотите вызвать оконную процедуру, 
используемую по умолчанию. Это выглядело бы так: 

    void Cls_OnCommand (HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) { 
    // выполняем обычную обработку 
    // обработка по умолчанию 
    FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, DefWindowProc); 
    } 

    Макросы FORWARD_WM_* принимают распакованные параметры сообщения и 
воссоздают их эквиваленты wParam и IParam, после чего вызывают указанную Вами 
функцию. В приведенном примере это DefWindowProc, но так же легко можно 
использовать SendMessage или PostMessage. Фактически, чтобы отправить 
сообщение какому-то окну в системе, можно применить макрос FORWARD_WM_* — это 
поможет скомбинировать отдельные параметры сообщения. Как и макросы 
HANDLE_WM_*, макросы FORWARD_WM_* для 16разрядной Windows и Win32 определены по-разному. 

    ____________________ Приложение Б. Распаковщики сообщений 661 


    Макросы для дочерних элементов управления 

    Эти макросы упрощают посылку сообщений дочерним элементам управления. Они 
очень похожи на макросы FORWARD_WM_*. Имя каждого из них начинается с типа 
управляющего элемента, которому передается сообщение, затем идут знак 
подчеркивания и имя сообщения. Например, чтобы послать сообщение LB_GETCOUNT 
окну списка, задействуйте такой макрос из файла WINDOWSX.H: 

    Sdefine ListBox_GetCount(hwndCtl) 
    ((int)(DWORD)SendMessage((hwndCtl), LB_GETCOUNT, 0, OL)) 

    Позвольте сделать несколько замечаний по этому макросу. Во-первых, у него 
только один параметр (hwndCtI) — описатель окна списка. Так как сообщение 
LB_GETCOUNT игнорирует параметры wParam и IParam, Вам вообще нет нужды 
беспокоиться о них. Макрос, как Вы уже видели, автоматически передаст нули. 
    Во-вторых, тип значения, возвращаемого SendMessage, преобразуется в int, в 
связи с чем не нужно самому преобразовывать тип. Обычно Вы пишете что-то вроде: 

    int n = (int) SendMessage(hwndCtl, LB_GETCOUNT, О, OL); 

    При компиляции этой строки в 16-разрядной Windows компилятор выдал бы 
предупреждение о возможной потере значащих разрядов. Причина в том, что Вы 
пытаетесь «втиснуть» значение типа DWORD (возвращаемое SendMessage) в 
переменную типа int. Гораздо проще написать: 

    int n = ListBox_GetCount(hwndCtl): 

    Кроме того, эта строка понятнее, чем строка с SendMessage, согласны? 
    Что мне не нравится в этих макросах, так это необходимость передавать им 
описатель окна элемента управления. Чаще всего элементы управления, которым Вы 
посылаете сообщения, являются дочерними окнами диалогового окна. Из-за этого 
придется все время вызывать GetDlgItem примерно так: 

    int n = ListBox_GetCount(GetDlgItem(hDlg, ID_LISTBOX)); 

    Этот код выполняется ничуть не медленнее, чем код с использованием 
SendDlgItemMessage, но объем программы увеличивается из-за дополнительных 
вызовов GetDlgItem. Если надо послать несколько сообщений одному и тому же 
элементу управления, Вы, возможно, захотите обратиться к GetDlgItem лишь раз, 
сохранить описатель дочернего окна, а затем вызвать все необходимые макросы: 

    HWND hwndCti = GetDlgItem(hDlg, ID_LISTBOX); 
    int n = ListBox_GetCount(hwndCtl): 
    ListBox_AddString(hwndCtl, "Another string"); 

    Если Вы пишете код именно так, приложение заработает быстрее, поскольку не 
будет повторных вызовов GetDlgItem. Эта функция вообще выполняется довольно 
медленно, если в диалоговом окне много элементов управления, а искомый элемент 
находится где-то в конце Z-цепочки. 

    662 ______________________________Windows для профессионалов 


    API-макросы 

    Они упрощают выполнение некоторых распространенных операций — например, 
создание нового шрифта, выбор его в контекст устройства и сохранение описателя 
исходного шрифта. Необходимый для этого код выглядит как-то вроде: 

    HFONT hfontOrig = (HFONT) Select0b]ect(hdc, (HGDIOBJ) hfontNew); 

    Этот оператор требует два преобразования типов, чтобы избежать 
предупреждений при компиляции. Как раз для этого и разработан один из 
макросов, определенный в файле WINDOWSX.H: 

    #define SelectFont(hdc, hfont) \ 
    ((HFONT) Select0bject((hdc). (HGDIOBJ) (HFONT) (hfont))) 

    При его использовании строка кода в программе станет такой: 

    HFONT hfontOrig = SelectFont(hdc, hfontNew); 

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