Opening a Window with Win32 on Windows

References

Win32 is the native Windows API. There have been many additions over the years, and multiple other APIs built on-top or alongside, but good ol' Win32 is still well-supported on Windows 11, with no end in sight.

The below code is the Win32-equivalent of, "Hello, World!"

Copy the above code into a file and name it main.c. Then open a terminal in the same directory and run the following command:

gcc main.c

You shouldn't see any output in your terminal, but in the folder an a.exe file should have been created. This is your executable binary file. Run it with ./a.exe (or double-click it). You should see a blank window pop up, then close after 5 seconds.


#define UNICODE #define _UNICODE #include <windows.h> #include <assert.h> #include <stdio.h>

windows.h defines most of what we need. Win32 has gone through many changes over the years, and it was initially designed without unicode. While UTF-8 is clearly the best character encoding, and most widely used today, Microsoft decided to implement UTF-16 instead. You'll find various Win32 functions with a trailing A or W, for ANSI or Wide (UTF-16). I recommend you always target the unicode API, which can be ensured by defining the two UNICODE macros above. I've chosen to include assertions as a way to help you catch bugs as you follow the Path, so you'll see them often.

int main() {

If you're familiar with Windows programming, you may have been expecting to see WinMain. Whether you need WinMain or main comes down to how your program is linked. I prefer to always use main() - you can still make both console and GUI applications this way as long as you get your linker flags right. If you want a console window for stdout output and such, you can either compile without any extra flags, or use -mconsole. For a Windows GUI application with no console window, use -mwindows.

const wchar_t window_class_name[] = L"Window Class"; const WNDCLASS window_class = { .lpfnWndProc = DefWindowProc, .lpszClassName = window_class_name, .style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC, .hCursor = LoadCursor (NULL, IDC_ARROW), }; { const auto result = RegisterClass (&window_class); assert (result); }

Before we create a window, we must define a window class with some information about the window(s) to be created. You're already seeing some of Microsoft's insane variable prefixes here Systems Hungarian notation). Since we're using the unicode API, strings must be wide char arrays (wchar_t). Wide char literals are made with an L before the first double-quotation mark. lpfnWndProc is the function that will be called to process all window messages (events), which include user input, resizing, etc. Windows has a default one called DefWindowProc, which we assign for now. CS_HREDRAW | CS_VREDRAW means the window should be redrawn on any resize. Setting the cursor to the standard arrow fixes the problem where your cursor gets stuck looking like the loading circle whenever it's over your window.

const auto window_handle = CreateWindow (window_class_name, L"Golden Path", WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL); assert (window_handle);

CreateWindow takes many arguments - check the reference for all of them. Note the wide character string literal for the title. The window styles here just make a normal application window. You can change the third and fourth CW_USEDEFAULTs to a width and height number to create a window with a set size.

MSG message; while (PeekMessage (&message, NULL, 0, 0, PM_REMOVE)) DispatchMessage (&message);

This is the most basic type of message handling loop for a window. PeekMessage checks if there are any messages in the queue, and PM_REMOVE means that if there is one, it'll be removed. DispatchMessage calls the WndProc we set in the window class earlier with the message. I chose PeekMessage here instead of GetMessage so that, once we process the initial messages and haven't received any new ones yet, the loop will exit and proceed onto the 5 second sleep and close.

gcc main.c

The code is built with a simple gcc call.