Win32 API programming with C - Window messages and creating the Window Procedure
Window Messages
A GUI application must respond to events from the user and from the operating system.
Events from the user include all the ways that someone can interact with your program: mouse clicks, key strokes...
Events from the operating system include anything "outside" of the program that can affect how the program behaves. For example, the user might plug in a new hardware device.
While your application is running, events can happen unpredictably and in varied sequences. The operating system communicates with your application by sending specific messages, each identified by a unique numeric code that corresponds to a particular event, like a user pressing the left mouse button, which triggers a specific message code to the window.
#define WM_LBUTTONDOWN 0x0201
Some messages have data associated with them. For example, the WM_LBUTTONDOWN message includes the x-coordinate and y-coordinate of the mouse cursor.
The Message Loop
Throughout its operation, an application processes numerous messages and might manage multiple windows, each linked to its specific window procedure. To handle these messages efficiently, the application uses a loop to collect and direct messages to the appropriate windows. The operating system allocates a hidden message queue for each thread that opens a window, preventing direct manipulation by the program.
Messages can be pulled from this queue using the GetMessage
function.
MSG msg;
GetMessage(&msg, NULL, 0, 0);
This function removes the first message from the head of the queue. If the queue is empty, the function blocks until another message is queued. The fact that GetMessage blocks will not make your program unresponsive. If there are no messages, there is nothing for the program to do.
Although the msg
structure contains information about the message, you will almost never examine this structure directly. Instead, you will pass it directly to two other functions.
TranslateMessage(&msg);
DispatchMessage(&msg);
The TranslateMessage function is related to keyboard input. It translates keystrokes into characters.
The DispatchMessage function tells the operating system to call the window procedure of the window that is the target of the message. In other words, the operating system looks up the window handle in its table of windows, finds the function pointer associated with the window, and invokes the function.
MSG msg;
while(GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Writing the Window Procedure
The DispatchMessage function calls the window procedure of the window that is the target of the message. The window procedure has the following signature.
LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
There are four parameters:
hwnd is a handle to the window.
msg is the message code.
wParam and lParam contain additional data that pertains to the message.
LRESULT is an integer value that your program returns to Windows. It contains your program's response to a particular message. The meaning of this value depends on the message code. CALLBACK is the calling convention for the function.
A typical window procedure is simply a large switch statement that switches on the message code. Add cases for each message that you want to handle.
switch (msg)
{
case WM_SIZE: // Handle window resizing
// etc
}
In the window procedure, cast the wParam and lParam parameters to the correct data type, and pass those values to the function. For example, to handle the WM_SIZE message, the window procedure would look like this:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_SIZE:
{
int width = LOWORD(lParam); // Macro to get the low-order word.
int height = HIWORD(lParam); // Macro to get the high-order word.
// Respond to the message:
OnSize(hwnd, (UINT)wParam, width, height);
}
break;
}
}
void OnSize(HWND hwnd, UINT flag, int width, int height)
{
// Handle resizing
}
Putting it all together
The following code will register the window class, create a window, write the message loop and write a Window Procedure that will handle WM_CLOSE and WM_DESTROY messages.
#include <windows.h>
// Step 4: the Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
//Step 1: Register the Window Class
char g_szClassName[] = "myWindowClass";
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX); // The size, in bytes, of this structure
wc.style = 0; // The class style(s)
wc.lpfnWndProc = WndProc; // A pointer to the window procedure.
wc.cbClsExtra = 0; // The number of extra bytes to allocate following the window-class structure.
wc.cbWndExtra = 0; // The number of extra bytes to allocate following the window instance.
wc.hInstance = hInstance; // A handle to the instance that contains the window procedure for the class.
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); // A handle to the class icon. This member must be a handle to an icon resource. If this member is NULL, the system provides a default icon.
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // A handle to the class cursor. This member must be a handle to a cursor resource. If this member is NULL, an application must explicitly set the cursor shape whenever the mouse moves into the application's window.
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); // A handle to the class background brush.
wc.lpszMenuName = NULL; // Pointer to a null-terminated character string that specifies the resource name of the class menu.
wc.lpszClassName = g_szClassName; // A string that identifies the window class.
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); // A handle to a small icon that is associated with the window class.
if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
// Step 2: Creating the Window
HWND hwnd;
hwnd = CreateWindowEx(
0, // Optional window styles.
g_szClassName, // Window class
"My window", // Window text
WS_OVERLAPPEDWINDOW, // Window style
CW_USEDEFAULT, // Position X
CW_USEDEFAULT, // Position Y
1024, // Width
768, // Height
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if(hwnd == NULL)
{
MessageBox(NULL, "Window Creation Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Step 3: The Message Loop
MSG msg;
while(GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
https://learn.microsoft.com/en-us/windows/win32/learnwin32/your-first-windows-program
Subscribe to my newsletter
Read articles from Ciprian Fusa directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Ciprian Fusa
Ciprian Fusa
I am a .NET Developer and Consultant with over 15 years of experience, I specialize in crafting scalable, high-performance applications that drive business growth and innovation.