Thông tin tài liệu
www.gameinstitute.com
Week
4
Game Institute
Introduction to C and C++
by Stan Trujillo
Introduction to C and C++ : Week 4: Page 1 of 34
www.gameinstitute.com
© 2001, eInstitute, Inc.
You may print one copy of this document for your own personal use.
You agree to destroy any worn copy prior to printing another. You may
not distribute this document in paper, fax, magnetic, electronic or other
telecommunications format to anyone else.
This is the companion text to the www.gameinstitute.com
course of the
same title. With minor modifications made for print formatting, it is
identical to the viewable text, but without the audio.
Introduction to C and C++ : Week 4: Page 2 of 34
www.gameinstitute.com
Table of Contents
Introduction to C++ 4
Lesson 4 – Application Architecture 4
The typedef Keyword 5
Handles 7
Macros 8
The HelloWin32 Sample 8
Windows Messaging 11
The Message Pump 12
Callback Functions 13
Window Creation 15
The SimpleWindow Sample 16
Class Frameworks 21
The GameApplication Class 21
The WinMain function 27
The WndProc Function 28
The SimpleWindowClass Sample 31
Message Pump Modifications 32
What’s next? 33
Introduction to C and C++ : Week 4: Page 3 of 34
www.gameinstitute.com
Introduction to C++
A GameInstitute course by Stan Trujillo
The console applications that we’ve been written so far are useful for game-related tools and utilities (and
instructional samples), but console applications are too limited for the game application itself.
What we need is a windowed application. This will let us create a window in which to display graphics,
and provide access to some important Windows-specific features. But this isn’t the only thing that a game
requires. Although games have a lot in common with most windowed applications, there are some
important differences. In this lesson we learn about windowed applications and high-level game
application design, and a few C++ topics that we still need to cover.
This lesson is different than the previous lessons. It introduces some real-world programming issues that
we haven’t had to use thus far, and it focuses on building a body of code that we can use for the
remaining lessons. It also moves more briskly than the previous lessons—meaning that not all of the code
presented is discussed in detail.
The reason for the increased pace is that this lesson is designed to bridge the gap between the simple and
educational samples from the previous lessons and the game that we’re building up to. This bridge takes
the form of a small class framework that encapsulates some of the more mundane elements of Windows
programming. The advantage is that once we’ve written this framework, we don’t have to worry about
how it works. This lesson moves quickly because there is a lot to cover, and because it is not necessary
for you to master the Windows programming details required to implement this framework.
On the other hand, it’s not enough to be given a class framework and being told not to worry how it
works. That’s why we’ll spend this lesson building the framework from the bottom up. What is important
to take from this lesson is the basics of Windows programming, the high-level design of a game
application, and some techniques that go into good class design.
Lesson 4 – Application Architecture
The console applications used in the previous lessons each had two portions: those that we provided, and
those and were provided in the form of functions from the standard libraries. The standard library
provided us with helpful features such as user input support, screen output support, and string
manipulation functions. All of these features are still available to us now that we’re going to be writing
windowed applications, but we’ll need more than just the standard libraries, which are largely platform
independent. What we need is Windows-specific features.
Microsoft provides access to Windows-specific features through Win32, which is essentially a huge set of
functions. Win32 is the Application Programming Interface (API) that is used to write Windows
applications. (The ‘32’ referrers to the fact that Win32 is specific to 32 bit versions of Windows.) There
are other APIs for writing Windows applications, but Win32 is the most direct way to access Windows
features.
Win32 is the product of an evolution that began with the first version of Windows. At the time, C was the
language of choice, so Microsoft designed Win32 based on C programming techniques. Win32 is still a
C-style, function based API, partly because there are so many existing applications that Microsoft can’t
change the underlying API without upsetting thousands of developers, and partly because Microsoft has
been relatively slow to adopt C++ in general.
Introduction to C and C++ : Week 4: Page 4 of 34
www.gameinstitute.com
For C++ programmers, Microsoft provides other APIs such as MFC (Microsoft Foundation Classes), and
ATL (Active Template Library). Instead of a collection of functions, these packages provide a collection
of classes that can be used for Windows application development. MFC in particular provides classes that
are designed to be used as a generic class framework from which any type of application can be written.
Both MFC and ATL are built “on top of” Win32, meaning that although they provide an alternative
interface, they are in fact written using Win32. As a result, most game programmers feel that using these
APIs for games is foolish because better performance can be gained by using Win32 directly. This is a
debatable issue, but for this course we’ll go with the status quo, and opt not to use MFC or ATL.
This doesn’t mean that we won’t be using classes to build the foundation required for games. It means
that we’ll be using Win32 to write a small and efficient class framework that is designed specifically with
games in mind. This framework can be small because, although Win32 is a massive API, most games
require the use of only a tiny fraction of the complete Win32 API.
Before we can do any of this, however, there are some topics that need to be covered first. For starters,
using Win32 requires knowledge of some language features that we haven’t discussed yet.
The typedef Keyword
C and C++ both support the typedef keyword, which is short for type definition. A type definition is
essentially a type alias. The typedef keyword can be used to define an alternative name for any data type.
typedef is used extensively in Win32. For example, Win32 defines aliases for most of the intrinsic types.
Consider these variable declarations:
INT i;
SHORT j;;
LONG l;
FLOAT f;
CHAR ch;
Each of these variables uses an intrinsic type, but through an alias that Win32 provides. The code in any
Windows application is free to use these types instead of the genuine intrinsic types. There’s no
difference between the two, because the genuine types are used with typedef to create these aliases, like
this:
typedef int INT;
typedef short SHORT;
typedef long LONG;
typedef float FLOAT;
typedef char CHAR;
The typedef keyword is followed by the name of an existing type, and ends with the desired alias and a
semicolon. typedef doesn’t create new data types—it creates new names for existing data types.
The examples shown above are of dubious value. They don’t provide any advantage over the native types
unless you happen to prefer upper-case data type names. But these are just a few of the type aliases that
Win32 provides. Some are useful merely because they are shorter than the alternatives. For example:
typedef unsigned short USHORT;
typedef unsigned char UCHAR;
typedef unsigned int UINT;
Introduction to C and C++ : Week 4: Page 5 of 34
www.gameinstitute.com
These type definitions provide shorthand syntax for the unsigned variation of the intrinsic types:
USHORT index;
Instead of
unsigned short index;
But Win32 doesn’t stop there—some new terms are used to describe familiar types. One example is the
term word. For a 32-bit operating system like Windows, a word is a 16-bit unsigned integer, and a double
word is a 32-bit unsigned integer. Hence these typedefs:
typedef unsigned short WORD;
typedef unsigned long DWORD;
Win32 also uses typedef to define names for some data types that are used for specific purposes. For
example, Windows is a message-based operating system. Messages are used to communicate with the
application code. Each message has two optional parameters, which are represented by the WPARAM
and LPARAM types, as defined here:
typedef UINT WPARAM;
typedef LONG LPARAM;
As we’ll see later, Windows messages are delivered by functions that provide the message itself, which is
represented by the UINT type alias, and is accompanied by parameters, like this:
int ProcessMessage(UINT msg, WPARAM wParam, LPARAM lParam);
At first glance, this function appears to use three exotic parameter types, but each is actually just a
typedef for an integer type.
Pointer types are also frequently assigned alternative type names by Win32. These typedefs have the
prefix “LP”, which is short for long pointer. The “long” refers to a designation made obsolete by 32-bit
operating systems, but is nevertheless still used. For example, LPDWORD is a pointer to the DWORD
type, and can be used like this:
DWORD value;
LPDWORD pValue = &value;
Despite the fact that no asterisk appears in the declaration of pValue, it is a pointer because the
LPDWORD type definition includes the asterisk. Win32 defines LPDWORD like this:
typedef DWORD* LPDWORD;
A number of similar type aliases are provided for char pointers:
typedef CHAR* LPSTR;
typedef const CHAR* LPCSTR;
These definitions make the following declarations possible:
LPSTR str = “Initial Text”;
Introduction to C and C++ : Week 4: Page 6 of 34
www.gameinstitute.com
LPCSTR const_str = “Const Text”;
Win32 uses these alternative type names extensively, so many of the function prototypes and structures
that Win32 provides for use in Windows programming look as though they are based on exotic data
types, but in fact most of the types used are simply intrinsic types. Nevertheless, the frequency at which
these types are used makes using Win32 a bit more difficult at first.
Handles
Win32 is designed around constructs that C programmers developed long before C++ was invented. One
of these constructs uses handles to represent entities that, had C++ been used, would be classes instead. A
handle is a data type that represents a specific entity such as a window, a bitmap, or an icon. When one of
these items is created, Win32 provides a handle that represents the new item. This handle can then be
used with functions that Win32 provides to manipulate the item.
A prominent example is the HWND data type, which serves as a handle for a window. (The “H” prefix
stands for handle, and WND is short for window.) When a new window is created, an instance of the
HWND type is used to store the handle value for that window. This handle can be used with a number of
functions what Win32 provides to manipulate that window. The ShowWindow function, for example, has
this prototype:
BOOL ShowWindow( HWND hWnd, int nCmdShow );
The first argument is the handle to the window that should be affected by the function call, and the second
argument is used to indicate the effect desired. The SW_SHOW symbol, for example, is provided to
indicate to ShowWindow that the window that the handle represents should be displayed if it is currently
hidden. Notice that the ShowWindow return type uses BOOL instead of the intrinsic type bool—yet
another type alias used by Win32.
Another important handle type is HINSTANCE, which essentially represents the application itself. As
we’ll see soon, Win32 provides an HINSTANCE handle to the application on startup.
The idea behind a handle is that it, together with the functions that accept handles as arguments,
encapsulate the functionality required for dealing with windows, applications, or any other programmatic
entity. Without classes, that’s the best that can be done to hide how a system works from the programmer.
If Win32 had been written using C++, then handles wouldn’t have been necessary.
Most of the time, handles are actually type aliases created with typedef that use either an integer or a
pointer as an underlying type. The HINSTANCE handle type, for example, is defined this way:
typedef void* HINSTANCE;
HINSTANCE is actually a void pointer. Pointers to the void type are generic pointers they are capable
of pointing to any type of data. The fact that Win32 defines HINSTANCE this way tells us that each
HINSTANCE is a pointer to a data type that is probably used by Win32 internally, but that we’re not
supposed to know about. The details of how Windows handles HWND and HINSTANCE are therefore
hidden from us—providing the C version of encapsulation.
Introduction to C and C++ : Week 4: Page 7 of 34
www.gameinstitute.com
Macros
We’ve intentionally avoided macros after mentioning them briefly in Lesson 1. In C++, macros can
almost always be avoided with superior features. For example, when a literal value has been required in
our programs, we’ve been using const, like this:
const int MaxPlayers = 10;
This is a better solution than the macro equivalent:
#define MaxPlayers 10
Both can be used in the same way:
SetMaxPlayers( MaxPlayers );
But the macro is a preprocessor command, whereas a constant is a C++ language construct. This means
that C++ understands constants in a way that it doesn’t understand macros. In the example above, if the
SetMaxPlayers function was modified to take a float instead of an int, the macro would cause a cryptic
compiler error, but the constant would result in type-mismatched error, more correctly reporting the
problem.
Nevertheless, Win32 uses macros liberally. Virtually every symbol used in conjunction with Win32
functions is a macro and not a constant. The SW_SHOW symbol used with the previously mentioned
ShowWindow function, for example, is defined like this:
#define SW_SHOW 5
These symbols would best be constants instead, but Win32 still uses macros. Luckily, programmers are
unaffected most of the time.
The HelloWin32 Sample
Unlike console applications, Windows applications don’t use main as a universal entry-point. Instead,
they use a similar function called WinMain. Each Win32 application must provide a version of this
function. Like main, WinMain is the first function called, and the last to exit. It also provides command-
line information to the application.
As we mentioned in Lesson 1, a windowed application that actually displays a window requires at least 50
lines of code or so. However, an application that doesn’t display a window can be much simpler. All you
need is the WinMain function. But Win32 applications don’t support the same output options available to
console applications, so, although you can send data to cout, there’s no place to display it. With no
output, there is very little point in running the resulting executable, but the code for the simplest possible
Win32 application looks like this:
#include <windows.h>
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
// all processing occurs here
return 0;
Introduction to C and C++ : Week 4: Page 8 of 34
www.gameinstitute.com
}
Using Win32 requires that the windows.h header file be included. This header file declares and defines
the core data types and functions necessary for writing Windows applications.
The WinMain function returns an int, but another symbol appears after the return type: APIENTRY.
This is a macro that Win32 defines to distinguish between the two types of function calling conventions
available in C and C++. These conventions define the exact manner in which a function call is performed
at the machine-language level. The standard method is used by default, and the pascal style is activated
by the pascal keyword. The APIENTRY macro resolves to the pascal keyword, which causes the
compiler to implement the WinMain function with the Pascal calling convention. For our purposes, all
we need to know is that APIENTRY or pascal must appear before the WinMain function name.
WinMain has four parameters. The first is the HINSTANCE that represents the application. This value
is required for some Win32 function calls, and is often stored in a global variable so that it is accessible to
all of the functions in the program. The second parameter is also an HINSTANCE, but is always zero for
Win32 applications (it was used for 16-bit versions of Windows), so it can be ignored.
The third argument is a string containing the command-line arguments used to launch the executable.
Unlike the argv parameter provided to the main function, this is not an array of strings, but a single string
containing all of the arguments as provided on the command-line. Windowed applications use command-
line arguments less frequently than console applications, so this parameter is often ignored.
The fourth and final WinMain argument is an integer that contains a value indicating the desired initial
state for the application’s window. Normally this value is equal to the SW_SHOWNORMAL symbol.
This initial version of WinMain doesn’t do anything except return zero. For the WinMain function, a
return value of zero indicates that the application is terminating without processing any messages,
which—in this case—is true.
This is the first point in this course in which we’ve had to implement a function that provides parameters
that we are unlikely to use. The second WinMain parameter in particular is useless, and we won’t need
the command-line arguments either so we can rewrite the definition of this function so that no parameter
name is given, like this:
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
// all processing occurs here
return 0;
}
The parameter type is mandatory, so we’ve left the type for the 2
nd
and 3
rd
parameters, but the parameter
name is unnecessary if it is unused, so we can safely omit the names for these two parameters.
Clearly, this is a pretty boring Win32 application, even if it is our first, because there is no output. We
can’t use cout, but we can use another form of Window output: a message box. Win32 provides the
MessageBox function for situations in which an application must display a message—usually an error
message. The MessageBox function prototype looks like this:
int MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
Introduction to C and C++ : Week 4: Page 9 of 34
www.gameinstitute.com
The first argument that MessageBox requires is the HWND of the application window. Since we don’t
have a window yet, we’ll use zero, which instructs Win32 to use the Windows desktop as the parent
window for the message box. The second argument is the text that is to appear inside the message box and
the third argument is the text to appear on the message box title bar.
The fourth argument is an integer that is used to control which icon (if any) appears on the message box,
and which buttons should appear. For example, using the MB_OK symbol causes the message box to
display just an “OK” button. We can display a message box by adding a call to MessageBox to
WinMain, like this:
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
MessageBox( 0, "Hello Win32!", "WinMain", MB_OK );
return 0;
}
This version of WinMain is used in the HelloWin32 sample, which displays this output:
Before we move on, we can use the MessageBox function to discuss the bit-wise OR operator (|). This
operator combines two integer values into a single value by setting the individual bits of the resulting
value according to the bits of the two source values. The OR operator manipulates values at the binary
level—something that we won’t get into in this course, but is an operation that is often used within an
API to allow one or more options to be enabled through a single parameter.
The MessageBox function uses this technique for the fourth parameter. Previously, we used the MB_OK
symbol to indicate that an “OK” button should be displayed, but Win32 provides more symbols for use
with the MessageBox function. The MB_ICONEXCLAMATION symbol, for example, indicates that
an icon containing an exclamation point should be displayed. We can modify our call to MessageBox to
use both of these options, like this:
MessageBox( 0, "Hello Win32!", "WinMain", MB_OK | MB_ICONEXCLAMATION );
The OR operator is used to separate the MB_OK and MB_ICONEXCLAMATION symbols. The result
is that the value of each symbol is combined into a single integer, which is provided to the MessageBox
function. Both symbols have an effect on the results, which look like this:
Introduction to C and C++ : Week 4: Page 10 of 34
[...]... two different uses in C++, depending on whether it appears inside a class definition For our purposes we’ll concentrate on how static affects data members and member functions when used with a class Consider this modification: class GameApplication { public: GameApplication() { assert( objectPresent == false ); objectPresent = true; } www.gameinstitute.com Introduction to C and C++ : Week 4: Page 22... TranslateMessage and then to DispatchMessage The while loop continues to pump messages until GetMessage returns false, which happens when the WM_QUIT message is posted www.gameinstitute.com Introduction to C and C++ : Week 4: Page 12 of 34 This message pump can be placed inside the WinMain function, like this: int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) { MSG msg; while (GetMessage(... call without using the function name, let’s take a closer look at how function calls work Consider this function: void Func(int val) { cout .
been relatively slow to adopt C++ in general.
Introduction to C and C++ : Week 4: Page 4 of 34
www.gameinstitute.com
For C++ programmers, Microsoft.
Introduction to C and C++ : Week 4: Page 2 of 34
www.gameinstitute.com
Table of Contents
Introduction to C++ 4
Lesson 4 – Application
Ngày đăng: 19/01/2014, 02:20
Xem thêm: Tài liệu Giáo trình C++ P4 docx, Tài liệu Giáo trình C++ P4 docx