Console (DOS) vs. Windows
At their core, our games are essentially going to be Windows applications. Writing a Windows application is entirely different than writing a console (DOS) application, and you’re going to know at least enough about Windows applications to get a game ‘wedged’ into the operating system.
First, both types of application have their own starting point. Take a quick look:
DOS Program:
int main()
{
return 0;
}
Windows Program:
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
return 0;
}
Both programs don’t do anything, but you can already see that there are differences. When you instruct MSVC to create a project for you, it’s important to select the proper application type (i.e. Win32 Application) or else the compiler is going to complain that it can’t find
main() – it’s trying to compile a console application (because of the project type) when you actually want to create a Win32 Application.
The Windows Programming Model
Here are the steps taken by a typical Windows application in order to get up and running:
- The program describes the type of window (on-screen) that it will ‘live’ in. This is referred to as registering the window class
- Now that the OS knows what type of window the application desires, it asks the OS to actually create a window based on the description that was registered. Coincidentally, this is referred to as creating the window.
- The application instructs the OS to display the window on the screen.
- A message loop is entered that receives Windows messages. This code loop continually checks to see if there are any OS messages available, and calls a window procedure to handle the message.
The first three items in the list are mere overhead (after all, the OS is called Windows, so it makes sense that we are to create a window on the display), and the fourth item is the real catch here. Let me quickly explain…
Once a Windows program enters its message loop, it’s basically sitting there waiting for something to happen. If the user isn’t clicking on something or typing away, the application has nothing to do (typically), so it continues executing its message loop code. In the event that something of interest to the application does happen, the OS lets the program know by sending it a message. Once a message is received by the application, it can then leave the message loop in order to process the message. Once message processing is complete, it’s back to the message loop again.
For this reason, Windows applications are referred to as
event-driven. If anything happens in the OS that a program is concerned with (e.g. the user clicked a button in the window), a message is created and sent to the application. This isn’t hard to visualize – take a look at most of the Windows programs on your machine… if you’re not doing anything, it usually isn’t either.
Let’s take a look at the code for a typical message loop:
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Here you can see that the message loop is implemented as a while statement. So long as
GetMessage() returns a ‘true’ value, the loop will continue to execute. Inside the message loop, accelerators are translated (keystroke combinations that have specific meanings), some internal message translation occurs, and finally the message is dispatched (i.e. a function in the program is called to handle the message). It’s not important to spend too much time investigating this code; most every Windows application has a similar message loop, so it’s standard code.
What types of messages would a Windows application receive? Well, there are hundreds of different messages, but I can give you some examples of more common messages:
- the user moves or resizes the window
- the mouse button is pressed while the mouse is over the window
- the user is typing on the keyboard while the application has the input focus
- a button or other control is activated
- the user is trying to close the application
I think that you get the idea. We’re not interested in delving too deep into the world of Windows application development – we’re here to make games, remember?
A Game in an Application
It seems to make sense that the typical Windows application is event-driven (i.e. responds to OS messages), but this is certainly a problem when it comes to writing a game. After all, games are constantly running (just like a DOS program), and yet Windows expects us to sit in a message loop waiting for events? Our first notion is to do away with the message loop altogether and just ‘plop’ our game right inside of
WinMain(). The reason why this is highly inadvisable is because Windows needs to be able to communicate with our program (e.g. to let us know when the user switches windows or is attempting to close the application).
What is typically done for a game is a slight modification of the message loop, like so:
// The main loop. This will call Game_Main() repeatedly, stopping
// only to process Windows messages, and terminating with WM_QUIT.
while(TRUE)
{
// Grab any Windows messages that are for us
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// Send these messages to WindowProc
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Call our Game function
else if (G.bActive) Game_Main();
}
What this message loop is doing is ‘peeking’ at the message queue for the application. If there’s nothing in it, we go ahead and call our own function (in this case called
Game_Main() ). What ends up happening is that our game function gets called over and over, and is interrupted only if there’s an OS message waiting for us.
This brings up an interesting point. If our game code is sitting inside of a function called
Game_Main(), that means it gets called repeatedly – not just once. It’s up to us to write our game code so that it works in this fashion…we’ll get to that later in the tutorial series. The important thing to note is that we have a technique for writing a game in Windows that behaves like a proper Windows application.
Windows Message Processing
When there’s a message directed to our application, it’s placed in a message queue – kind of like ‘taking a number’. The messages are taken, one at a time (first-come-first-served) and handed over to another function in our application for processing. You’ve seen the function responsible for handing the message over; it’s called
DispatchMessage(). What isn’t apparent though is how the message makes it to another function in our program. When we registered our window class on startup, we provided to the OS the name of the function that will handle all Windows messages for our application. We’ll be looking at a complete example in a moment.
As for processing the messages themselves, take a look at this piece of code:
//////////////////////////////////////////////////////////////////////////////
// WindowProc
//
// Whenever the operating system has a message for us, it ends up here. If
// we''re interested in any particular message, we have to provide code to
// handle it.
//
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Process the window message
switch (msg)
{
case WM_ACTIVATE:
// The active state of our window is changing (e.g. ALT-TAB).
// We keep track of this so that we don''t bother drawing
// frames of animation if it can''t be seen anyhow...
G.bActive = !((BOOL)HIWORD(wParam));
return 0L;
case WM_KEYDOWN:
if (wParam == VK_ESCAPE)
{
// The ESCAPE key quits...
G.bQuitting = TRUE;
PostQuitMessage(0);
return 0L;
}
break;
case WM_DESTROY:
// We''re done; quit the main loop
PostQuitMessage(0);
return 0L;
}
// Pass all other messages on for default processing
return DefWindowProc(hWnd, msg, wParam, lParam);
}
The Windows messages are numerical, and their names start with
WM_ . This particular example cares about
WM_ACTIVATE,
WM_KEYDOWN and
WM_DESTROY. For all of the other hundreds of messages possible, we pass them off to a function called
DefWindowProc(), which is supplied by Windows to deal with whatever we don’t. As you can see, this example doesn’t care about a whole lot. If it doesn’t concern terminating our application or switching programs in Windows, we let the OS take care of the housekeeping and we get back to our code inside of
Game_Main().
The Least I Could Do…
Let’s sum up, shall we? We really want to be a Windows application, because Windows applications get to use the system’s hardware automatically, and we can use components like DirectX for our game’s input, output, sound, etc. The rules for being a Windows application include registering a window class, creating a window, and providing a message loop so that the OS can talk to you. Games however need to be constantly running, and can’t merely respond to messages in order to execute. As a compromise, we supply all of the Windows application prerequisites, and wedge a call to our game code in the message loop so as to maximize execution time.
For the purposes of this article, I’m not going to show you any complete sample code. If you’re interested, you can summon the powers of MSVC to create a sample Win32 Application for you and investigate the file(s) it generates, but you’ll be going through more trouble than it’s worth. When we start with the actual game development exercises, you’ll be introduced to the actual Windows component of the game template code, and because you took the time to read this article, things will naturally make sense.
We’ve fulfilled our obligation to Windows, so we can move on to our obligation to gamers.
Questions or comments? Please, reply to this topic.