I’m not sure what the correct thing to do with the base windows application on a game nowadays is. Logic says that a good old CreateWindow call will be the fastest and less resource hungry way because if it worked back for Windows 1 then it must be fast. On the other hand UWP is the new hotness for windows and if we’re using modern libraries like DirectX 12 we can guarantee that the user is on Windows 10 of one flavour or another.

I think the best bet is to just support both as an experiment. There’s very little actual windows code needed in a game anyway, it’s not as if we’ll be using standard controls, so I’m going to start here.

Starting with Win32 the big thing to know is that a window is identified by a handle of type HWND. You get very used to seeing that as it’s used a lot. Also you need to remember that Win32 is a C style API that originally predates C++ and so we register a C function, or in our case something that looks like it, that receives messages whenever anything interesting happens. For my purposes I’m wrapping it all in a C++ class because I’m not a monster.

My first stab at a windows class looks simple enough, but some alarm bells should already be ringing when you see that static this_ pointer. I’ll explain that in a bit.

class window
{
public:
	explicit window(HINSTANCE handle, int nCmdShow);
	virtual ~window() = default;

	int run();

	void on_update(HWND hwnd);
protected:
	void on_destroy(HWND hwnd);
	void on_create(HWND hwnd, CREATESTRUCT* createstruct);
	void on_paint(HWND hwnd);
	void on_size(HWND hwnd, UINT size_mode, WORD width, WORD height);
	
private:
	virtual LRESULT window_procedure(HWND window_handle, UINT message, WPARAM wParam, LPARAM lParam);
	static LRESULT CALLBACK static_window_procedure(HWND window_handle, UINT message, WPARAM wParam, LPARAM lParam);
	static window* this_;
};

The program entrypoint is a variation on main() called WinMain. This gives us some useful information like the HINSTANCE for the application which is a way of identifying which copy of our application we are if multiple copies are running. The second HINSTANCE variable is a holdover from 16bit windows and can be ignored. The final two are the command line parameters and an integer saying how windows wants our window to be created. This is how options like “start maximized” are handled. Since we just need the instance handle and to know what to do with our window we just pass these through.

You’ll notice the odd wWinMain and LPWSTR names. This is because Windows had to make a decision about UNICODE early on and it turns out that although the decision was probably good at the time it’s now turned into an awkward mess. We have char arrays and wchar_t arrays and all the APIs want wchar_t ifs you’re compiling for UNICODE mode. This is a headache for later, but for now we just need to know that we can create a wchar string by using L”string” and ignore it for now.

int wWinMain(HINSTANCE inst, HINSTANCE, LPWSTR, int show)
{
	window win(inst, show);
	return win.run();
}

You’ll also need to make sure that your linker options in Visual Studio are set to be for the windows subsystem so that it knows that WinMain is the entrypoint rather than the more traditional main()

Visual Studio Linker Options

For this example I can do all my initialization in the constructor, but that would be coded in a more robust way in a production application so that error handling can be better. You’ll notice that we don’t do any here although the odds of any of these functions failing is rather low and if Windows can’t even manage to do these things then odds are you’re a few milliseconds from a bluescreen anyway.

window::window(HINSTANCE handle, int nCmdShow)
{
	WNDCLASSEXW wcex{};
	const auto window_name = L"window_name";

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = window::static_window_procedure;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = handle;
	
	wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);

	wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);

	wcex.lpszMenuName = nullptr;
	wcex.lpszClassName = window_name;
	

	RegisterClassExW(&wcex);

	const HWND window_handle = CreateWindowExW(0, window_name, L"Window", WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, handle, this);

	ShowWindow(window_handle, nCmdShow);
	UpdateWindow(window_handle);
}

This is a lot more straight forwards than you’d think considering that Win32 has a bit of a reputation for being verbose and needing a lot of boilerplate code to get started. First a windows class is registered with the name “window_name”. We then call CreateWindowEx on and pass in this window class name.

The annoying thing is that any windows function that takes a string exists in two forms, one ending with A for char and one with W for wchar_t so we have CreateWindowExA and CreateWindowExW, and to really mess things up we have a macro that automatically selects which of these to use called CreateWindowEx. Because this is a macro if we ever create a function called CreateWindowEx for anything else it will get renamed to have A or W added to it. There’s lots of useful function names that windows implements that will do this and it will trip you up at some point.

We finish off with calls to ShowWindow and UpdateWindow, which do what they sound like they do, and we’re done. Windows will go through all the process of creating and displaying our window. All we need now is a bit of plumbing for our windows procedure callback.

Now we need to turn our attention to the window::static_windows_procedure function and why we’re passing a this pointer into the CreateWindow call.

This won’t be ideal, but I’m making a big assumption here that we’re a game and we’re only ever going to have one window and so I can get away with storing the this pointer in a static variable that’s shared between all instances of this class.

window* window::this_;

LRESULT window::static_window_procedure(HWND window_handle, UINT message, WPARAM wParam, LPARAM lParam)
{
	if (nullptr == this_)
	{
		switch (message)
		{
		case WM_NCCREATE:
			this_ = static_cast<window*>(reinterpret_cast<LPCREATESTRUCT>(lParam)->lpCreateParams);
			break;
		case WM_GETMINMAXINFO:
			return 0;
		};
	}

	if (nullptr != this_)
	{
		return this_->window_procedure(window_handle, message, wParam, lParam);
	}
	
	return 0;
}

What we have here is a static function because Windows doesn’t understand C++ and adding a normal C++ method would fail because there’s effectively a hidden this pointer as the first parameter added to a C++ method. Making it static makes it match the expected signature and because windows allows you to pass custom information into the CreateWindowEx function which we used by passing the this pointer.

Windows is based on messages. A window sits there in a loop and gets sent lots of useful information such as WM_SIZE when your window resizes. Everything from the screensaver being activated to the machine being shutdown is sent to through this function, so we need to handle a bunch of these messages. We are however not in our class instance so we need to take that passed in this pointer and call a specific copy of windows_procedure for it.

When a window is created a message called WM_NCCREATE is called just prior to the window being created. A CREATESTRUCT struct is passed in with the parameters we passed to CreateWindow, including our this pointer stored in lpCreateParams. We put this into our static this_ variable and from then on we can use that to call our specific window_procedure function.

WM_NCCREATE will be the second message that we receive. We also get WM_GETMINMAXINFO which we can safely ignore as it’s just used to override the window’s default size and position and we don’t care about it. This is lucky as it comes in before our this pointer and it somewhat messes up our model.

Now that we’ve sent each message to our actual window class instance we can handle each of the messages.

LRESULT window::window_procedure(HWND window_handle, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch(message)
	{
	case WM_CREATE:
		on_create(window_handle, reinterpret_cast<CREATESTRUCT*>(lParam));
		break;
	case WM_DESTROY:
		on_destroy(window_handle);
		break;
	case WM_PAINT:
		on_paint(window_handle);
		break;
	case WM_SIZE:
		on_size(window_handle, static_cast<UINT>(wParam), LOWORD(lParam), HIWORD(lParam));
		break;
	default:
		return DefWindowProc(window_handle, message, wParam, lParam);
	}
	return 0;
}

Here we switch on message, which is one of a bazillion options that luckily we can mostly ignore. As you can see parameters are passed in through two variables called wParam and lParam so we need to know how to extract the correct values from these. There may be more than two parameters, as we have here with the WM_SIZE message but the Windows documentation tells you what needs to be done. For each message we want to handle we decode the parameters and call a usefully named and structured function where we can actually handle them. You’ll also notice that we’re passed in the HWND identifier for the window to each function and we don’t need to store that value as a member variable.

There is actually a slightly nicer way to crack apart these messages in the header windowsx.h, but it hasn’t been updated for more recent messages and doesn’t really save much work in the end. The help also doesn’t really mention the message handling side of it any more so we’ll pretend it no longer exists. We would just get lots of C style cast and precision loss warnings in our code anyway.

Lastly we need to interact with Windows with something called a message loop.

int window::run()
{
	MSG msg{};

	while (WM_QUIT != msg.message)
	{
		if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			on_update(msg.hwnd);
		}
	}

	return static_cast<int>(msg.wParam);
}

I’m being clever here and using a more game specific one that handles any messages that are available and then if none are calls a function called on_update() where we can do our rendering and game logic. This is because we only get windows messages sent to us when things happen and for the majority of the time on Windows things just sit there waiting. If you did do all your drawing and logic in on_paint here you’d notice that windows would decided that it didn’t need to update the window very often at all.

There are two problems with this code that we need to address through handling how we respond to messages that are sent to us. The first can be seen here in the loop as WM_QUIT is never actually going to get sent if we try and close the window. This is because we aren’t doing anything with WM_DESTROY.

void window::on_destroy(HWND hwnd)
{
	::PostQuitMessage(0);
}

This is very simple and just makes sure that our window is destroyed when needed. Eventually we might want to expand this out so that we can have a “are you sure you want to quit” message instead of just shutting if the user hits alt+F4.

Lastly we’ll just be being spammed by WM_PAINT messages and on_update will never be called. This may seem like a good thing as we want to draw as many frames as we can as quickly as we can, but it’ll cause no end of headaches so we need to tell Windows that we’re on top of things in WM_PAINT.

void window::on_paint(HWND hwnd)
{
	ValidateRect(hwnd, nullptr);
}

What we’re doing here is basically just saying that it’s fine, the window is painted as we need it. That’ll get to be more complicated in a real game of course.

And that’s it. We can add some empty functions for the two other useful messages and we’re done.

void window::on_create(HWND hwnd, CREATESTRUCT* createstruct)
{
}

void window::on_size(HWND hwnd, UINT size_mode, WORD width, WORD height)
{
}

They’ll be very useful in a game engine, but for now we can ignore them. WM_CREATE is called after our window is created but before it is shown and WM_SIZE is sent whenever the window size changes.

Conclusion

That’s all it takes in order to create a window on Windows. There’s a lot more that needs to be thought about in a real game of course, but we have something on screen in far less lines of code than the daunting amount that creating a C win32 windowed app using the Visual Studio template would suggest. Next time I’ll make it display some 2D text and graphics.