Win32 Programming
In this blog we're going to explore some common things you might want to do when programming for window. This includes things like opening a window, watching the file system, drawing things to the window, and even playing sounds. The Microsoft Developer Network (MSDN) is the canonical source for more information on the win32 API and how to use it. Window often has two versions of any given function, for example CreateWindowW
and CreateWindowA
. The W version of a function take a unicode wide string, while the A version takes an ascii string. These functions are also #defined as CreateWindow, which is defined depending on your project configuration.
Let's start with opening a new Window. Opening a window takes two functions, WinMain
and WndProc
. WinMain
is the entry point of a win32 program. It gets called automatically, like main
in a non win32 program. This function is responsible for creating a window, and pumping messages to the window. WndProc
in turn is responsible for handling messages that are sent to the window.
These functions can be called whatever, WinMain
and WndProc
are just the convention i use. In fact, you can open a Win32 window from a console application, which is useful if you want to keep watching the console for logs (Torque3D does this). You can call WinMain
manually, like so:
int main(int argc, char** argv) { return WinMain(GetModuleHandleA(NULL), NULL, GetCommandLineA(), SW_SHOWDEFAULT); }
Let's start by including windows.h, and forward declaring the functions we're about to implement.
#include < windows.h > LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow);
To open a window, we need to creat a "class" for it. A class is a string identifier that windows uses as a "prototype" for the window. It contains the title of the window, some display information, and most importantly a pointer to the windows message processing function. You can use the same window "class" to create multiple windows. Let's register a new class called "Win32_Sample", pointing to a message function "WndProc".
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { WNDCLASSA wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wndclass.lpszMenuName = 0; wndclass.lpszClassName = "Win32_Sample"; RegisterClassA(&wndclass);
Next, let's set the size of the window. A window is the size of the client area (what you draw to) plus the size of any decorations (the title bar, thick frames to resize, etc...). We want to define the size of the client area, but we will need to know the full size of the window (including any decorations). Luckily, we can find the window area given the client area using the AdjustWindowRect
function, like so:
int clientWidth = 800; int clientHeight = 600; RECT rClient; SetRect(&rClient, 0, 0, clientWidth, clientHeight); AdjustWindowRect(&rClient, WS_OVERLAPPEDWINDOW | WS_VISIBLE, FALSE); int windowWidth = rClient.right - rClient.left; int windowHeight = rClient.right - rClient.left;
Now it's time to open the window! We want to open the window in the middle of the screen, so we will first need to find the width and height of the screen. Then all we need to do is call CreateWindowW
or CreateWindowA
. This function will return a handle to the newly created window, this handle is important because it is used to work with the window. The first argument is the class name we registered with RegisterClassA
, the next two arguments are the title and style of the window. These are followed by the x and y position and the width and height of the window. The last few arguments can be null.
After calling CreateWindow
we need to call ShowWindow
and UpdateWindow
for the window to display and start pumping messages.
int screenWidth = GetSystemMetrics(SM_CXSCREEN); int screenHeight = GetSystemMetrics(SM_CYSCREEN); HWND hwnd = CreateWindowA(wndclass.lpszClassName, "Window Title", WS_OVERLAPPEDWINDOW | WS_VISIBLE, (screenWidth / 2) - (windowWidth / 2), (screenHeight / 2) - (windowHeight / 2), windowWidth, windowHeight, NULL, NULL, hInstance, 0); ShowWindow(hwnd, SW_NORMAL); UpdateWindow(hwnd);
Finally, the last thing we need to do is run the windows message loop. We're actually going to have two loops, an application loop and a window message loo;. The message loop is simple, we need to call PeekMessage
to get the current message, if we have a message we need to call TranslateMessage
which lets windows do some magiv to the message, and finally DispatchMessage
to invoke the message handling function of the window. The second argument to PeekMessage
below is NULL
, which handles messages for any window that is running in the current thread. Alternateley, you could pass in a window handle to specify which window to handle. After the message i call Sleep
to keep my laptops fans from going crazy, this is where you would handle your per frame application logic.
MSG msg; bool quit = false; while (!quit) { // WM_QUIT does not belong to any window, hence the second argument NULL while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) { quit = true; break; } // Optionally, check if (msg.hwnd == hwnd) { TranslateMessage(&msg); DispatchMessageA(&msg); // } // End of optional hwnd check } Sleep(1); }
After the application loop quits, call any cleanup code and return the w param of the last message (or 0).
return (int)msg.wParam; }
Let's implement a minimal window message processing function. At a minimum, we need to handle the WM_CLOSE
and WM_DESTROY
messages. On close, we need to call DestroyWindow
, which will post a destroy message. On destroy we need to post a quit message to the window with PostQuitMessqage
, which will post the WM_QUIT
message that WinMain
uses to break out of the application loop. Call DefWindowProcA
for any message that you do not handle specifically.
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { switch (iMsg) { case WM_CLOSE: DestroyWindow(hwnd); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcA(hwnd, iMsg, wParam, lParam); }
Full code listing: https://gist.github.com/gszauer/8560bc64cf008ade2c80c480b6495b9a