Вполне очевиден выход — разнести обработку сообщений по отдельным функциям, которые будут вызываться из процедуры обработки сообщений. Однако для каждого сообщения передаются свои данные, упакованные в двух параметрах — wParam и lParam. Иногда они не используются, иногда содержат какие–либо значения, иногда — указатели. Естественно, было бы удобным передавать в вызываемую функцию уже распакованные параметры. Затруднение здесь вызывает то, что для Windows API и для Win32 API одни и те же данные одного и того же сообщения могут быть упакованы по разному[18].
При разработке windowsx.h это все было учтено (для Windows API и для Win32 API распаковщики определяются по разному). Так, для каждого сообщения WM_xxx определен свой макрос с именем HANDLE_WM_xxx. Например, для сообщения WM_CREATE определен макрос:
HANDLE_WM_CREATE(hwnd, wParam, lParam, fn)
Параметры всех макросов одинаковые, что позволяет передавать им непосредственно параметры сообщения (окно–адресат hwnd, параметры wParam и lParam), а также имя функции–обработчика fn. Этот макрос должен использоваться внутри конструкции switch для вызова нужной функции и передачи ей распакованных параметров. Например фрагмент следующего вида:
 switch ( uMsg ) {
 case WM_CREATE: return HANDLE_WM_CREATE(hWnd,wParam,lParam,fnOnCreate); 
 // ...
 }
будет превращен компилятором в следующий фрагмент (подробнее см. исходный текст windowsx.h):
switch ( uMsg ) {
 case WM_CREATE:
 return ((fnOnCreate)((hWnd),(LPCREATESTRUCT)(lParam)) ?
0L : (LRESULT)-1L); 
// ...
 }
То есть при раскрытии макроса HANDLE_WM_xxx осуществляется распаковка параметров, вызов функции и анализ возвращаемого результата. Здесь, кстати, скрыта одна ловушка (по счастью крайне редкая): результат, возвращаемой функцией–обработчиком не всегда будет совпадать с результатом, описанным в справочнике для данного сообщения. Случай с WM_CREATE именно такой — согласно описанию обработчик WM_CREATE должен вернуть 0L, если все в порядке. А, как мы видим в приведенном фрагменте, функция, вызываемая распаковщиком, должна вернуть TRUE, то есть не 0, если все в порядке (распаковщик сам заменит TRUE на 0L).
При рассмотрении этого примера возникает вопрос — а как должна быть описана функция–обработчик, что бы распаковщик ее правильно вызывал? Ответ прост — в самом файле windowsx.h перед определением соответствующего макроса приводится прототип этой функции. То есть нам надо сделать следующее: открыть windowsx.h, найти в нем строку, где определяется распаковщик для WM_CREATE (это легко делается поиском) и посмотреть на приведенный там текст:
/* BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) */
 #define HANDLE_WM_CREATE(hwnd, wParam, lParam, fn) \
 ((fn)((hwnd), (LPCREATESTRUCT)(lParam)) ? 0L : (LRESULT)-1L)
 #define FORWARD_WM_CREATE(hwnd, lpCreateStruct, fn) \
 (BOOL)(DWORD)(fn) ((hwnd), WM_CREATE, 0L, \
 (LPARAM)(LPCREATESTRUCT)(lpCreateStruct))
Описание функции Cls_OnCreate мы и ищем. Далее нам надо его просто скопировать в наше приложение и исправить при желании имя функции. Единственное, что остается не слишком удобным — так это вызов макроса–распаковщика — уж очень длинная строка получается. Для этого в windowsx.h содержится отдельный небольшой макрос:
HANDLE_MSG( hWnd, uMsg, fn )
Используется он таким способом:
 switch ( uMsg ) {
 HANDLE_MSG( hWnd, WM_CREATE, Cls_OnCreate );
 // ...
 }
При этом он сам вставляет “case WM_xxx: return...” и прочее. Важно следить, что бы в описании оконной процедуры параметры wParam и lParam назывались именно так и не иначе. Дело в том, что HANDLE_MSG при обращении к макросу HANDLE_WM_xxx указывает ему именно эти имена.
Чтобы закончить разговор о распаковщиках сообщений надо ответить только на два вопроса — зачем нужны макросы FORWARD_WM_xxx, определенные в том–же windowsx.h и как можно добавить распаковщики для каких–либо сообщений, там не определенных (например, нестандартных).
Рассмотренные пока макросы–распаковщики позволяли нам вызвать функцию–обработчик сообщения, имея для нее данные, упакованные в wParam и lParam. Однако иногда возникает другая необходимость — имея параметры функции передать какое–либо сообщение. Для этого предназначены макросы FORWARD_WM_xxx. Для использования этих макросов, необходимо, что–бы функция, получающая параметры сообщения, имела следующий вид:
LRESULT proc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
Макросы FORWARD_WM_xxx получают в качестве параметров распакованные данные (как и функция–обработчик), упаковывают их в параметры сообщения и вызывают указанную функцию. По счастью практически все функции, которые придется вызывать с помощью макросов FORWARD_WM_xxx (SendMessage, PostMessage, DefWindowProc и пр.) соответствуют приведенному описанию.
Например, сообщение WM_SETFONT посылается окну (стандартного класса) для того, что бы назначить ему нужный шрифт. Параметры этого сообщения следующие: wParam содержит хендл шрифта, а младшее слово lParam указывает, надоли перерисовывать окно сразу после смены шрифта. Предположим, что ваше окно имеет дочернее окно, и вам хочется сделать так, чтобы при смене шрифта в вашем окне одновременно менялся шрифт в дочернем. Соответственно вы должны включить в оконную процедуру обработку сообщения WM_SETFONT и в его обработчике передать такое–же сообщение дочернему окну.
void Cls_OnSetFont( HWND hwnd, HFONT hfont, BOOL fRedraw );
 LRESULT WINAPI _export proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
 switch ( uMsg ) {
 // ...
 HANDLE_MSG( hWnd, WM_SETFONT, Cls_OnSetFont );
 // ...
 }
 }
// ...
 void Cls_OnSetFont( HWND hwnd, HFONT hfont, BOOL fRedraw )
{
 HWND hwndChild = ...; // определениехендладочернегоокна
 FORWARD_WM_SETFONT( hwndChild, hfont, fRedraw, SendMessage );
 }
Здесь, кстати, можно было бы воспользоваться макросом SetWindowFont из того же windowsx.h. Этот макрос обращается к FORWARD_WM_SETFONT, как в рассмотренном примере, однако текст при этом становится более читаемым:
 void Cls_OnSetFont( HWND hwnd, HFONT hfont, BOOL fRedraw )
{
 HWND hwndChild = ...; // определение хендла дочернего окна
 SetWindowFont( hwndChild, hfont, fRedraw );
 }
Добавление собственных распаковщиков не должно вызвать больших затруднений — достаточно только разработать реализации макросов HANDLE_WM_xxx и FORWARD_WM_xxx аналогично уже сделанному в windowsx.h.
Этот пример иллюстрирует применение распаковщиков сообщений на примере простейшего приложения. Фактически он соответствует слегка измененному примеру 1A, в котором оконная процедура переписана для использования распаковщиков сообщений. Функция WinMain в этом примере осталась без изменений.
#defineSTRICT
 #include <windows.h>
 #include <windowsx.h>
#define UNUSED_ARG(arg) (arg)=(arg)
static char szWndClass[] = "test window";
BOOL Cls_OnCreate( HWND hwnd, LPCREATESTRUCT lpCreateStruct )
{
 UNUSED_ARG( hwnd );
 UNUSED_ARG( lpCreateStruct );
 return TRUE;
 }
void Cls_OnPaint( HWND hwnd )
{
 PAINTSTRUCT ps;
 BeginPaint( hwnd, &ps );
 TextOut( ps.hdc, 0, 0, "Hello, world!", 13 );
 EndPaint( hwnd, &ps );
 }
void Cls_OnDestroy( HWND hwnd )
{
 UNUSED_ARG( hwnd );
 PostQuitMessage( 0 );
 }
LRESULT WINAPI _export WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
 switch ( uMsg ) {
 HANDLE_MSG( hWnd, WM_CREATE, Cls_OnCreate );
 HANDLE_MSG( hWnd, WM_PAINT, Cls_OnPaint );
 HANDLE_MSG( hWnd, WM_DESTROY, Cls_OnDestroy );
 default: break;
 }
 return DefWindowProc( hWnd, uMsg, wParam, lParam );
 }
static BOOL init_instance( HINSTANCE hInstance )
{
 WNDCLASS wc;
 wc.style = 0;
 wc.lpfnWndProc = WinProc;
 wc.cbClsExtra = 0;
 wc.cbWndExtra = 0;
 wc.hInstance = hInstance;
 wc.hIcon = LoadIcon( NULL, IDI_APPLICATION );
 wc.hCursor = LoadCursor( NULL, IDC_ARROW );
 wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
 wc.lpszMenuName = NULL;
 wc.lpszClassName = szWndClass;
 return RegisterClass( &wc ) == NULL ? FALSE : TRUE;
 }
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
 UNUSED_ARG( lpszCmdLine );
 MSG msg;
 HWND hWnd;
 if ( !hPrevInst ) {
 if ( !init_instance( hInst ) ) return 1;
 }
 hWnd= CreateWindow(
 szWndClass, // class name
 "window header", // window name
 WS_OVERLAPPEDWINDOW, // window style
 CW_USEDEFAULT,CW_USEDEFAULT, // window position
 CW_USEDEFAULT,CW_USEDEFAULT, // window size
 NULL, // parent window
 NULL, // menu
 hInst, // current instance
 NULL // user-defined parameters
 );
if ( !hWnd ) return 1;
 ShowWindow( hWnd, nCmdShow );
 UpdateWindow( hWnd );
 while ( GetMessage( &msg, NULL, NULL, NULL ) ) {
 TranslateMessage( &msg );
 DispatchMessage( &msg );
 }
return msg.wParam;
 }
Здесь мы рассмотрим некоторые основные особенности реализации объектно–ориентированного программирования в Windows. В последнее время получили огромное распространение библиотеки объектов для создания приложений в среде Windows. Особенно широко они стали распространяться с развитием систем визуального программирования. Наибольшее распространение получили библиотеки объектов компаний
· Borland — Object Windows Library (OWL), поддерживается компиляторами Borland C++ (рассматривается версия v2.5, сопровождающая компилятор Borland C/C++ v4.5).
· Microsoft — Microsoft Foundation Classes (MFC), поддерживается наибольшим количеством компиляторов, среди которых Microsoft Visual C++, Watcom C++, Symantec C++ и другие (рассматривается версия v4.0, сопровождающая Visual C/C++ v4.0).
Такие библиотеки достаточно многофункциональны и громоздки, размер исполняемого файла, созданного с их помощью редко бывает меньше 300–400K. Конечно, при разработке больших систем, поддерживающих такие инструменты как OLE, DAO или WOSE, регистрирующих свои собственные типы файлов и т.д., использование этих библиотек может существенно сократить время, необходимое для разработки приложения.
Эти библиотеки объектов, хотя и имеют огромное количество различий, неизбежно имеют и много общего, что определяется платформой, на которой они работают — Windows. Для обеспечения эффективной работы приложений эти библиотеки вынуждены предусматривать простой механизм доступа посредством методов объектов к функциям API, что их неизбежно сближает между собой. Кроме того реализация и иерархия объектов в Windows неизбежно приводит к появлению чем–то сходной иерархии классов в библиотеках ООП.
В этом разделе мы рассмотрим простейшее приложение в среде Windows, построенное средствами ООП, причем все классы будут оригинальными — ни MFC, ни OWL не применяется. Это сделано для того, что бы “извлечь” на поверхность некоторые аспекты разработки классов для Windows–приложений. Здесь будут использоваться существенно упрощенные методы реализации объектов, по сравнению с “большими” библиотеками.
Возможно, что в некоторых частных случаях использование такого подхода может оказаться и более продуктивным, чем применение MFC или OWL. Особенно, если ваше приложение похоже на простейшее “Hello, world!” (в этом случае, правда, еще удобнее может быть обойтись совсем без классов).