In the Win32 API, messages are used for initiating tasks and passing information
in the system. For example, a message may be sent to a window with a SendMessage
function call to notify it of a change in its size:
SendMessage(hWnd, WM_SIZE, static_cast<WPARAM>(state), MAKELPARAM(cx, cy));
(Note: Typically the system itself sends the WM_SIZE message when appropriate.)
The state, cx, and cy variables are all
parameters that provide additional information about the WM_SIZE
message. These are "packaged" into two parameters that are sent with every Windows
message: WPARAM and LPARAM.
A window processes its messages in a special function called a window procedure.
This procedure often contains a large switch statement that catches the messages the window
is interested in and "cracks," or parses, their parameters out of the
WPARAM and LPARAM message parameters.
LRESULT CALLBACK WndProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
switch (message)
{
case WM_SIZE:
{
UINT state = static_cast<UINT>(wParam);
int cx = LOWORD(lParam);
int cy = HIWORD(lParam);
// do something here with state, cx, and cy.
return 0;
}
default:
break;
}
return DefWindowProc(hWnd, messsage, wParam, lParam);
}
This doesn't look too bad, but Windows applications written this way can become
complex rather quickly as the number of messages being handled increases, and the
size of the switch begins to grow. Also, extracting the values from
wParam and lParam is rather tedious and can be a source
of errors if the developer isn't careful.
To make the job a little easer, a header file named windowsx.h
is provided with the Win32 API. This header defines several macros called
message crackers that simplify the packing and extraction of message
parameters.
#define HANDLE_MSG(hwnd, message, fn) \
case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
// ...
/* void Cls_OnSize(HWND hwnd, UINT state, int cx, int cy) */
#define HANDLE_WM_SIZE(hwnd, wParam, lParam, fn) \
((fn)((hwnd), (UINT)(wParam), (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam)), 0L)
#define FORWARD_WM_SIZE(hwnd, state, cx, cy, fn) \
(void)(fn)((hwnd), WM_SIZE, (WPARAM)(UINT)(state), MAKELPARAM((cx), (cy)))
Using these macros, the above switch statement could be simplified.
void MyWindow_OnSize(HWND hwnd, UINT state, int cx, int cy)
{
// do something here with state, cx, and cy.
}
LRESULT CALLBACK WndProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
switch (message)
{
HANDLE_MSG(hWnd, message, MyWindow_OnSize);
default:
break;
}
return DefWindowProc(hWnd, messsage, wParam, lParam);
}
This is simpler, but it's not a panacea. Macros do not provide type-safety, and
when used in this manner they tend to obfuscate normal C/C++ syntax. A switch
statement is supposed to contain case blocks, yet the one shown above
doesn't appear to contain any.
MFC has its own mechanism for handling and dispatching messages, which I won't go into
here, but it relies upon an even more elaborate system of macros. For an excellent
description and analysis of the mechanism I refer the reader to the book
MFC Internals
by George Shepherd and Scott Wingo.