Introduction

Hello, and welcome back to Introduction to WinAPI. We are now at Lesson 3, which means we have already registered a window class, created a visible window, and built the message loop that keeps the app responsive. In this lesson, we take two important steps: we move the Window Procedure into its own source file to keep our project organized, and we implement a robust shutdown process so our application exits gracefully.

As you may recall, DispatchMessageW sends each message to WindowProc. Now we will structure the program more cleanly while guiding the window through its entire lifecycle—from creation to a clean exit.

Why Separate The Window Procedure

Before we look at code, it helps to build the idea clearly. A Window Procedure is a callback; Windows calls our function when something happens to the window. Putting this callback in a separate file is useful because each file gets a clearer job: main.cpp focuses on application startup and the message loop, while wndproc.cpp focuses on reacting to messages. As the program grows, this split keeps the design easier to follow and extend.

Declaring the Shared Callback in a Separate File

In earlier units, the procedure lived in the same file as WinMain. Now the actual function body lives in wndproc.cpp, which means main.cpp needs a declaration (a prototype) so the compiler knows the function's signature.

In larger projects, you would typically place this declaration in a header file (.h) and include it. For now, we will declare it directly in main.cpp.

This declaration ensures main.cpp knows the exact signature Windows expects. The return type LRESULT, the parameters, and the CALLBACK calling convention must match perfectly, or the linker will fail to connect the two files.

Registering and Running the Loop in main.cpp

With the declaration in place, main.cpp can now reference WindowProc when setting up the window class. This decouples the "how to start" from the "how to react."

When DispatchMessageW runs, the system looks up the function pointer we stored in wc.lpfnWndProc and jumps to our code in wndproc.cpp.

Understanding the Shutdown Sequence

In WinAPI, closing a window and terminating the application are distinct steps. Windows treats shutdown as a sequence of messages, giving us control over each stage:

  1. WM_CLOSE: Windows asks whether the window should be closed (triggered by the 'X' button or Alt+F4).
  2. DestroyWindow(hwnd): Our code confirms the request and initiates the destruction of the window.
  3. WM_DESTROY: Sent when the window is being destroyed. The application should treat the window as going away and perform any final cleanup.
  4. PostQuitMessage(0): We post WM_QUIT to the queue so the GetMessageW loop in main.cpp can finally return 0 and exit.
Implementing the Callback in wndproc.cpp

Now we move to the new file. This implementation uses a switch statement to interpret the incoming uMsg. The hwnd identifies the window, while wParam and lParam carry extra data for specific messages.

By handling WM_CLOSE and WM_DESTROY separately, we maintain a professional shutdown flow. DestroyWindow triggers the destruction, and PostQuitMessage ensures the process doesn't linger as a "zombie" in the background after the window is destroyed.

Default Handling and Fallbacks

Not every message needs custom logic. For example, WM_PAINT is sent when a window needs to be redrawn, but if we aren't ready to draw custom graphics yet, we let the system handle it.

DefWindowProcW is the system's default handler. It manages standard tasks like window resizing, title bar clicks, and basic painting. Any message that isn't finished with a return 0 should fall through to this function to keep the window behaving like a standard Windows application.

Conclusion and Next Steps

In this lesson, we improved our architecture by separating responsibilities across two files and implemented a clean shutdown path. We've learned that main.cpp manages the application's heartbeat, while wndproc.cpp handles its reactions. We also distinguished between a close request (WM_CLOSE) and actual destruction (WM_DESTROY).

With this structure in place, we are ready to practice building the callback connection and handling these messages ourselves. This foundation is essential as we move toward adding more complex features like user input and custom graphics.

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