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.
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.
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.
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.
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:
WM_CLOSE: Windows asks whether the window should be closed (triggered by the 'X' button or Alt+F4).DestroyWindow(hwnd): Our code confirms the request and initiates the destruction of the window.WM_DESTROY: Sent when the window is being destroyed. The application should treat the window as going away and perform any final cleanup.PostQuitMessage(0): We postWM_QUITto the queue so theGetMessageWloop inmain.cppcan finally return0and exit.
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.
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.
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.
