Every native Windows graphics application — whether it uses DirectX, OpenGL, or Vulkan — sits on top of the Win32 API. Before we can render a single pixel, we need to understand the host environment that owns our window, dispatches input, and routes events between the OS and our code.
You do not need to know the whole Win32 API to follow along. For this series, the goal is much smaller: understand just enough of the windowing layer to create a window, keep it alive, react to resize and input events, and give DirectX a place to present rendered frames.
Here is the practical reason this matters: when the user resizes your window, Windows sends your app a WM_SIZE message. Later, your DX12 code will respond by resizing its swap chain back buffers. When the user closes the window, Windows sends close and destroy messages. Your app needs to turn those into a clean shutdown. Win32 is the layer where those moments enter your program.
What You Should Know First
This article assumes you are comfortable with basic C++: functions, structs, pointers or references, and switch statements. You do not need prior Windows programming experience. If terms like HWND, HINSTANCE, or WndProc look strange, that is expected — we will define them as we go.
You also do not need to understand every corner of Windows desktop programming. We will skip menus, dialogs, GDI drawing, accelerators, COM, DPI scaling, threading, and most of the thousands of messages Win32 supports. Those are real topics, but they are not required to open a window for a graphics program.
The Big Picture
A Win32 desktop app is built around three things:
- An entry point (
WinMain) where execution starts. - A window, which is a kernel object the OS owns and your app holds a handle to (
HWND). - A message loop that pulls events out of a thread-local queue and dispatches them to a function you write (the window procedure).
Unlike a console program that runs top-to-bottom and exits, a Win32 app spends almost its entire life inside the message loop, reacting to events.
The rough lifecycle looks like this:
WinMain starts
↓
Register a window class
↓
Create a window
↓
Show the window
↓
Run the message loop
↓
WndProc handles messages
↓
Quit when the window is destroyed
If you remember only one idea from this page, remember this: a Windows app is not a single straight line of code. It is a small setup phase followed by a loop that waits for messages from the operating system.
Another way to divide it:
- Setup code runs once. Register the window class, create the window, and show it.
- Runtime code keeps running. The message loop stays alive, and
WndProcreacts to messages as they arrive.
A Small Win32 Glossary
Win32 names can feel cryptic at first because many of them come from older C-style APIs. Here are the terms you will see constantly:
HWND— a handle to a window. It is not the window object itself; it is an ID-like value your code uses when talking to Windows about that window.HINSTANCE— a handle to your running program module. You usually receive it inWinMainand pass it into setup functions.LRESULT— the return type used by a window procedure. It lets your handler return message-specific results to Windows.WPARAM/LPARAM— extra message data. Their meaning depends on the message. For example, a key message may store the key code inwParam.- Message — a small event packet from Windows. Examples include “the window was resized”, “a key was pressed”, or “the user clicked close”.
- Message queue — the list of pending messages waiting for your app’s thread.
- Window procedure — the callback function you write to handle messages for a window.
DefWindowProc— the default Windows message handler. You call this for messages your app does not handle itself.
WinMain vs main
Console apps use int main(...). GUI apps use:
int WINAPI WinMain(
HINSTANCE hInstance, // handle to this app's loaded module
HINSTANCE hPrevInstance, // legacy, always nullptr on modern Windows
LPSTR lpCmdLine, // command line as ANSI
int nCmdShow); // initial window show state
hInstance is the most important parameter — it’s how the OS identifies your running module, and you’ll pass it to nearly every window-creation API.
The all-caps words in the signature are old Windows calling conventions and typedefs:
WINAPItells the compiler which calling convention the Windows API expects.LPSTRmeans “long pointer to string” in older Windows naming. In modern code you will often use the wide-character version ofWinMaininstead.CALLBACK, which you will see onWndProc, is another calling convention macro. It tells Windows and your compiler how that function will be called.
You do not need to memorize these right now. Treat them as part of the required shape of Win32 functions.
The Event-Driven Model
Win32 is push-based. The OS doesn’t call your code directly when the user clicks; it posts a message (a small struct with a code and two parameters) into your thread’s message queue. Your message loop pulls those messages out one at a time and routes each to the right window’s procedure.
In plain English, your app does not constantly ask, “Did the user click? Did the user resize? Did the user type?” Instead, Windows leaves messages in a queue, and your app reads them one at a time:
The message loop is the code that performs that read-and-dispatch work:
MSG msg = {};
while (GetMessage(&msg, nullptr, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
GetMessage waits until a message is available. TranslateMessage does some keyboard-message translation, and DispatchMessage sends the message to the correct window procedure. In a real-time renderer, we will usually use PeekMessage instead so the app can keep rendering even when there is no input. The next article shows that version.
A window procedure is just a function with a fixed signature:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
// ... handle other messages
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
The big switch statement is the heart of every Win32 app. DefWindowProc is the default handler — anything you don’t care about, you forward to it so Windows can do the right thing (close the window on Alt+F4, repaint when needed, etc.).
One useful way to read WndProc is: “For the messages I understand, do my custom behavior. For everything else, let Windows handle it normally.”
Common Messages You’ll See
A few messages come up constantly when building a graphics app:
WM_CREATE— fired once, right after the window is created. A good place for one-time setup.WM_SIZE— the user resized the window. You’ll need to recreate or resize the swap chain’s back buffers.WM_PAINT— Windows wants you to repaint. With DX12 you typically ignore this and render on your own clock.WM_KEYDOWN/WM_KEYUP— keyboard input.WM_MOUSEMOVE,WM_LBUTTONDOWNetc — mouse input.WM_CLOSE— the user clicked the X. Default behavior is to destroy the window.WM_DESTROY— the window is being destroyed. PostWM_QUIThere so the message loop exits.
Where DirectX Fits In
DX12 doesn’t render to the screen directly. It renders to off-screen buffers called the back buffers of a swap chain. The swap chain is connected to your Win32 window through its HWND.
The flow looks like this:
Your DX12 commands render into a back buffer
↓
The swap chain owns that back buffer
↓
You call Present()
↓
Windows' desktop compositor receives the finished image
↓
The image appears inside your window on the monitor
So the Win32 window is the bridge between your GPU work and the user’s screen. DirectX produces images, but Win32 gives those images a real place to appear.
Things That Feel Weird at First
If this is your first Win32 program, a few details may feel backwards:
- The
HWNDis a handle, not the window itself. Windows owns the actual window. Your app stores a handle so it can refer to that window later. WM_CLOSEandWM_DESTROYare not the same message.WM_CLOSEmeans the user or system asked the window to close.WM_DESTROYmeans the window is actually being destroyed.DefWindowProcis part of normal control flow. You are not “giving up” by calling it. You are letting Windows handle all the default behavior you did not override.- A blocking message loop is fine for many apps, but not for games.
GetMessagewaits until something happens. A render loop usually usesPeekMessageso it can keep drawing frames even when there is no input. - The names are old because the API is old. Win32 has decades of history behind it. The patterns matter more than memorizing every type name immediately.
Once those ideas click, Win32 becomes much less mysterious. You set up a window, keep pumping messages, handle the few events your graphics app cares about, and let Windows handle the rest.
Quick Checkpoint
You are ready for the next article if these ideas make sense:
WinMainis where a Windows GUI app starts.HWNDidentifies the window your app created.- Windows describes events using messages like
WM_SIZE,WM_CLOSE, andWM_DESTROY. - The message loop keeps the app alive and dispatches messages.
WndProcis where your app handles messages for a window.DefWindowProchandles the default behavior for messages you ignore.- DX12 will use the window’s
HWNDwhen it creates a swap chain.
What’s Next
Now that you understand the structure, the next post walks through actually creating one — registering a window class, calling CreateWindowEx, and writing a working message loop. From there we’ll be ready to bring DX12 into the picture.