Introduction

Hello, and welcome back to Introduction to WinAPI. We are now at Lesson 2, which means we already know how to register a window class and create a visible window. Now, we add the missing piece that keeps the program alive: the application message loop.

As we may recall from the previous lesson, creating and showing a window is not enough by itself. If WinMain reaches return immediately, the process ends and the window disappears with it. In this lesson, we will keep the application responsive by reading messages from Windows, preparing keyboard input when needed, and routing those messages to our window procedure.

Thinking In Messages

Windows programs are event-driven. Instead of running a long sequence of steps and finishing, a graphical application spends most of its time waiting for something to happen: a key press, a mouse move, a repaint request, or a close action.

That is where the message loop comes in. Windows places events into a message queue for the thread, and our program repeatedly pulls one message at a time from that queue. It then decides what to do with each message or hands it to the window procedure. This design is important because it keeps the program efficient; rather than constantly checking for input, the application waits until the system has actual work for it.

Carrying Forward The Program Setup

Before we build the loop itself, we maintain the same setup from the earlier units. We still need the Windows header, a forward declaration for the window procedure, and the WinMain entry point. We also still register the window class, because a message loop is only useful if there are a real window and a real procedure ready to receive messages.

This part gives us the same foundation as before, but now we can see it in a larger flow. WindowProc is declared early because WinMain refers to it when filling wc.lpfnWndProc. hInstance connects the class to this program instance, and CLASS_NAME gives Windows a label it can use during creation. The registration check remains important: if the class is not registered, the later steps have nothing valid to work with.

Creating A Window Worth Listening To

Once the class exists, we create a window instance and make it visible. This section is familiar from the previous lesson, but it matters even more now because this window is the object that will begin receiving messages from the operating system.

CreateWindowExW asks Windows to build a top-level window from the class we registered. The returned HWND is our handle to that window. If creation fails, we stop immediately because there is nothing to display or manage. ShowWindow uses nCmdShow to respect the system's preferred display state, and UpdateWindow asks for painting work to happen promptly. At this point, the window can appear, but the application still needs a loop to remain active.

Preparing The Message Container

Now, we reach the new part of the lesson. Before reading anything from the message queue, we need a place to store each message that Windows provides. For that, WinAPI uses the MSG structure.

Think of msg as a small container that will be filled repeatedly as the program runs. It can hold the message identifier, the target window handle, time information, cursor position, and other related values. We initialize it with zeros so it starts in a clean state before the first call to the loop. This structure does not process anything by itself; it simply carries information from the queue to the rest of the program.

The Loop and its States

With msg ready, we can build the persistent loop. The key idea is to keep asking Windows for the next message until the application is told to quit or an error occurs.

GetMessageW is the function that keeps the application alive. It waits until a message is available and fills the msg structure. It is important to note that GetMessageW has three distinct return states:

  1. Greater than 0: A message was successfully retrieved. The loop continues.
  2. Zero (0): A WM_QUIT message was received. This is the signal to stop the loop and exit the application.
  3. Negative one (-1): An error occurred (such as passing an invalid window handle). We check for this specifically to prevent the program from behaving unexpectedly or getting stuck.
Processing the Message

Once GetMessageW gives us a valid message (a positive return value), the loop performs two more tasks to route the message correctly.

The loop handles each message using these two functions:

  1. TranslateMessage: This is primarily for keyboard input. It turns certain key-related messages into character messages, which becomes useful when we later care about text entry.
  2. DispatchMessageW: This routes the message to the correct procedure, which is WindowProc for our window class.

A useful detail here is that the queue belongs to the thread, not directly to the window. That is why the loop resides in WinMain, while the window-specific handling happens elsewhere. When the loop finally ends (because bRet was 0), we return msg.wParam as the program's exit code. That value usually comes from PostQuitMessage, which we will connect next.

Signaling The Loop To Stop

The loop does not stop automatically. It stops because the window procedure eventually posts a WM_QUIT message to the thread queue. In this lesson, we do that when the window is being destroyed.

This code closes the loop between the queue and the procedure. When WM_DESTROY arrives, PostQuitMessage(0) places a quit message into the thread's queue. The next time GetMessageW checks the queue, it returns 0, the while condition fails, and WinMain exits cleanly with the same value in msg.wParam. For all other messages, DefWindowProcW provides the standard default behavior, which keeps the window acting like a normal Windows window.

Conclusion and Next Steps

In this lesson, we completed the core flow of a basic WinAPI application. We carried forward the setup from earlier units, created and showed the window, introduced the MSG structure, and built the loop around GetMessageW, TranslateMessage, and DispatchMessageW. We also connected the loop to WindowProc by handling WM_DESTROY and calling PostQuitMessage(0).

The big idea to remember is this: the window procedure handles individual messages, but the message loop is what keeps the whole application running and responsive. With that foundation in place, the practice section is a great chance to assemble the loop ourselves and start thinking like a real Windows application.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal