Peeking Inside a Windows Program


Peeking Inside a Windows Program

Although I could certainly go on and on about the Win32 API, the best way to get you closer to creating a Windows game is to take a look at what actually goes into a Windows program. The next few sections break apart the major aspects of a Windows program, along with the code that makes them tick. It's not terribly important for you to understand every line of code at this point because much of what goes into a generic Windows program is overhead code that you won't bother with again once you dive into game creation. Nevertheless, it's important to at least have a feel for what is required of every Windows program.

Where It All Begins

If you come from the world of traditional C/C++ programming in non-graphical environments, you are no doubt familiar with the main() function. The operating system calls main() when a program is first run, and your program code starts executing inside main() . There is no main() function in a Windows program. However, Windows offers a similar function called WinMain() that serves as the starting point for a Windows program. Unlike main() , WinMain() simply creates and initializes some things and then eases out of the picture. After WinMain() creates a main window for the program, the rest of the program executes by responding to events in the main window procedure. The window procedure is where most of the really interesting things take place in a Windows program.

The Window Class

All windows in a Windows program are created based on a window class , which is a template that defines the attributes of a window. Multiple windows can be created from a single window class. For example, there is a standard Win32 window class that defines the attributes of a button, and all buttons in Windows are created from it. If you want to create a new window of your own, you have to register a window class and then create a window from it. In order to create new windows from a window class, the window class must be registered with Windows using the RegisterClassEx() Win32 API function. After a window class has been registered, you can use it to create as many windows as you want.

Window classes are represented by a data structure in the Win32 API called WNDCLASSEX , which defines the attributes of a window. Following is how the WNDCLASSEX structure is defined in the Win32 API:

 typedef struct _WNDCLASSEX {   UINT        cbSize;   UINT        style;   WNDPROC     lpfnWndProc;   int         cbClsExtra;   int         cbWndExtra;   HINSTANCE   hInstance;   HICON       hIcon;   HICON       hIconSm;   HCURSOR     hCursor;   HBRUSH      hbrBackground;   LPCSTR      lpszMenuName;   LPCSTR      lpszClassName; } WNDCLASSEX; 

This code reveals some of those strange Win32 data types I talked about earlier. It isn't important right now to go through each and every member of this structure. Instead, let's focus on a few of the more interesting members :

  • lpfnWndProc A pointer to the window procedure for the window class

  • hIcon The icon for the window class

  • hIconSm An optional small icon for the window class

  • hCursor The mouse cursor for the window class

  • hbrBackground The background brush for the window class

These members hopefully make some sense because they are related to fairly obvious parts of a window. The first member, lpfnWndProc , is probably the trickiest because it is a pointer to the window procedure for the window class; you find out what this procedure looks like in a moment. The hIcon and hIconSm members are used to set the icons for the window class, and they correspond to the program icons you see when a program is running in Windows. hCursor is used to set a special mouse cursor (pointer) for the window class if you decide you want something other than the standard arrow cursor. And finally, hbrBackground is used to set the background for the window class, which is the color that fills the background of the inside of the window. Most windows use white as a background color, but you're free to set it to any color you want.

Again, it's not imperative that you feel totally comfortable with the window class structure at this point. The goal right now is just to get acclimated with Win32 programming to a degree in which we can assemble a complete program. Later in the lesson, you put the window class structure to use in creating a minimal Windows program.

Creating a Window

A critical part of any Windows program, including games , is the creation of the main program window. Creating a window involves using a window class, which you learned about in the previous section. Although window classes define general characteristics for a window, other attributes of a window must be defined when a window is created. These attributes are provided as arguments to the CreateWindow() function, which is the Win32 API function used to create windows. Following is an example of creating a window using the CreateWindow() function:

 hwnd = CreateWindow(szAppName,   "My Game",   WS_OVERLAPPEDWINDOW,   CW_USEDEFAULT,   CW_USEDEFAULT,   CW_USEDEFAULT,   CW_USEDEFAULT,   NULL,   NULL,   hInstance,   NULL); 

It's not terribly important to understand every line of this code, but it is possible to focus on a few interesting aspects of the window creation. First of all, the name of the window class is specified as the first argument, szAppName . The second argument is the window title, "My Game" , which is displayed in the title bar of the window when the program is run. The WS_OVERLAPPEDWINDOW style is a standard Win32 style that identifies a traditional window that can be resized. The four CW_USEDEFAULT styles indicate the initial XY position of the window on the screen, as well as the window's width and height; you can use specific numbers for these settings, but CW_USEDEFAULT tells Windows to use a reasonable default value. The remaining parameters aren't terribly important right now, so we won't bother with them at the moment.

Keep in mind that I'm not expecting you to immediately absorb all this information; the main goal here is to start getting familiar with the general structure of the CreateWindow() function and its arguments. Notice that CreateWindow() returns a window handle to the newly created window. It's also worth pointing out that I could've used numeric values when specifying the window's X position, Y position, width, and height. For example, the previous code could have used hard-coded values such as , , 640 , and 480 . In fact, it is often helpful for games to use a fixed window size, which is why you'll eventually be plugging in real numbers for the width and height of your game windows.

Handling Messages

Earlier in the lesson, you learned that Windows communicates with your program by sending it messages. Let's take a closer look at messages to see how they work. A message has three pieces of information associated with it:

  • A window

  • A message identifier

  • Message parameters

The window associated with a message is the window to which the message is being sent. The message identifier is a number that specifies the message being sent. The Win32 API defines numeric constants that represent each message. For example, WM_CREATE , WM_PAINT , and WM_MOUSEMOVE are all numeric constants defined in the Win32 API that identify messages associated with window creation, window painting, and mouse movement, respectively.

The message parameters consist of two pieces of information that are entirely specific to the message being sent. These 32-bit values are called wParam and lParam , and their meaning is completely determined by the message being handled. For example, the wParam parameter for the WM_SIZE message contains information about the type of sizing performed on the window, whereas the lParam parameter contains the new width and height of the window's inside area, which is also known as the window's client area . The width and height are packed into the low and high words of the 32-bit lParam value, which is a common approach that Win32 uses to shove two pieces of information into a single location. If low words and high words sound intimidating, don't worry because I show you exactly how to extract useful information from lParam later in the book when you need it.

When a message is delivered to a program by Windows, it is processed in the WndProc() function. Although WndProc() is responsible for handling messages for a given window class, your program must still take on the task of routing messages to the appropriate window procedures. This is taken care of by a message loop , which must be placed in the heart of the WinMain() function:

 while (GetMessage(&msg, NULL, 0, 0)) {   TranslateMessage(&msg);   DispatchMessage(&msg); } 

This code essentially processes all messages at the application level and routes them to the appropriate window procedures. Code in each different window procedure is then responsible for taking action based on the messages they receive. If window procedures sound mysterious at this stage, read on to learn how they work.

The Window Procedure

Every window in a Windows program has an associated window procedure , which is a special function that is capable of being called by Windows. Windows calls a window procedure to deliver messages to a given window. You can think of a window procedure as a message processing function. In object-oriented terms, a window procedure is the behavioral part of a window object, whereas Windows maintains the data part of the object. Figure 2.1 shows how a window procedure fits into the object-oriented concept of a window.

Figure 2.1. In object-oriented terms, a window is an object whose behavior is determined by a window procedure and whose data is managed by Windows.

graphics/02fig01.gif

Window procedures are actually associated with window classes, which means that multiple windows created from a single class share the same window procedure. This is logical because the behavior of a given class of windows should be the same. A window procedure is defined in the Win32 API like this:

 LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam); 

Don't sweat it if this prototype looks a little intimidating. You are only concerned right now with the meaning of the arguments, which should be somewhat familiar from the previous explanation of messages:

  • hwnd Handle of the window to which the message is being sent

  • iMsg Message identifier

  • wParam Primary message parameter

  • lParam Secondary message parameter

Practically every WndProc() function contains a large switch statement that is responsible for separating the messages being handled. Following is an example of a WndProc() function with a very common piece of code that handles the WM_DESTROY message, which is passed to a window whenever it is being destroyed :

 LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {   switch (iMsg) {     case WM_DESTROY :       PostQuitMessage(0);       return 0;   }   return DefWindowProc(hwnd, iMsg, wParam, lParam); } 

Notice that the switch statement operates on the iMsg argument, and in this case only looks for the WM_DESTROY message. The PostQuitMessage() function call causes the application to quit because the main window is being destroyed. This code gets called when you click the X in the upper right corner of a window. Notice in this code how any messages not handled in the switch statement are passed through to the DefWindowProc() function. This is a strict requirement of all window procedures because DefWindowProc() is responsible for the default handling of messages. If you left this code out, some strange things would happen in your program.

Working with Resources

You might not realize it, but as a Windows user you are already very familiar with an important part of Windows programming: resources. Resources are special data items associated with a Windows program that typically define portions of a program's user interface. Unlike data such as variables used in program code, resources are stored in a program's executable file and loaded only as needed. Following are some of the standard resources supported by Windows:

  • Bitmap

  • Cursor

  • Dialog box

  • Icon

  • Menu

In addition to these standard resources, Windows also supports user-defined resources , which are custom resources that you write special code to handle. User-defined resources are very important in game programming because they allow you to include sound effects and music with a program as resources.

Resources are defined in a program using a resource script , which is a text file with a .RC extension. You use a resource compiler to compile a resource script into a binary resource file with a .RES extension. This binary resource file is then linked with an application's object code to create a complete executable application. The process of compiling and linking resources to an application's .EXE file is usually a standard part of a Windows compiler's overall build process; in other words, you shouldn't have to worry about manually compiling resource scripts. Following is an example of a simple resource script that defines an icon and a cursor:

 IDI_MINE ICON   "Mine.ico" IDC_MINE CURSOR "Mine.cur" 

You might initially be a little confused by the fact that resources are compiled into a binary form, especially considering that resources don't have any C/C++ code directly associated with them. The compilation process for resources is actually quite different from the process for C/C++ code. A special resource compiler is required to compile resources, but its main job is to assemble all the resources defined in a resource script into a single binary file. A compiled binary resource file is also very different from an object file of compiled C/C++ code. You can think of a compiled resource file as a group of resources combined into binary form. Even though compiled resource files aren't directly related to object files, they are merged together at the link stage of application development to create an executable application file (.EXE).

Perhaps the most commonly used resource is the icon, which forms a vital part of the user interface for Windows applications. If you don't specifically set a custom icon for an application, the default Windows icon is used. Fortunately, it's very simple to set a custom icon for an application. In fact, it only involves two steps:

  1. Define the icon in the application's resource script.

  2. Load the icon when you define and register the application's window class in the WinMain() function.

The first step is accomplished by adding a single line to the resource script for your application:

 IDI_MINE ICON "Mine.ico" 

This code uses the ICON resource statement to define an icon named IDI_MINE that is stored in the file Mine.ico . When the resource script is compiled, the icon will be referenced from the Mine.ico file and compiled into the binary resource file. The size of the icon can be either 32x32 or 16x16, depending on how you are going to use it; the default icon size for an application is 32x32. In the next section of the lesson, you learn how to define both a small (16x16) and a large (32x32) icon for the Skeleton program example.

The second step to setting a custom icon involves loading the icon and assigning it to the hIcon field of the WNDCLASSEX structure used to define a window class. You do this by calling the LoadIcon() Win32 API function and passing in the name of the icon:

 wndclass.hIcon = LoadIcon(hInstance, "Mine.ico"); 

The hInstance parameter to LoadIcon() is passed into the WinMain() function by Windows, and is a handle to the application instance. This handle references the executable file from which the icon resource is to be loaded.



Sams Teach Yourself Game Programming in 24 Hours
Sams Teach Yourself Game Programming in 24 Hours
ISBN: 067232461X
EAN: 2147483647
Year: 2002
Pages: 271

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net