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
Trang 14 Game Institute
Introduction to C and C++
by Stan Trujillo
Trang 2© 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
Trang 3Table 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
Trang 4Introduction 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
Trang 5For 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:
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;
Trang 6These 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”;
Trang 7LPCSTR 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
Trang 8Macros
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:
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;
Trang 9}
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 line Windowed applications use line arguments less frequently than console applications, so this parameter is often ignored
command-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)
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);
Trang 10The 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:
Trang 11When an API provides symbols for use in activating options, the symbols used to represent each optional
behavior are called flags In this case we added the MB_ICONEXCLAMATION flag to activate an option that the MessageBox function supports
Windows Messaging
Even with output, our first windowed application is a humble beginning We managed to display a
message box, which is a window, but one that is provided by Windows We need to create our own window so that we can control its size and contents This will require knowledge of how Win32
communicates with each window
Windows is a message-based operating system It uses messages to enable communication between the operating system and each application For example, if the user presses a key, and your application
window is currently the top-most, or active window, Windows delivers a WM_KEYDOWN message to
your application This message is accompanied by information that indicates which key was pressed
Likewise, when the user releases the key, a WM_KEYUP message is posted to your application
Windows defines hundreds of different messages Each is delivered for a different reason Some messages include extra information about the message, and some messages don’t The handling of these messages
is a vital part of any windowed application, but luckily most messages can be passed back to Windows without any processing This is because Windows provides a default message handling mechanism that will handle a message in a generic fashion
This table contains some of the messages that often warrant custom processing:
that is to be redrawn
WM_MOUSEMOVE Sent whenever the mouse is moved—includes
the new location of the mouse
WM_LBUTTONDOWN Sent whenever the left mouse button is
WM_KEYDOWN Sent whenever a key is pressed—includes the
ID of the key involved
Trang 12WM_KEYUP Sent whenever a key is released—includes
the ID of the key involved
WM_ACTIVATE Sent to indicate any change in the application
window state, such as when the window gains
In order enable the processing of messages, a windowed application must provide three things not present
in the previous sample: a message pump, a message handling callback function, and a window
The Message Pump
A message pump is a loop that retrieves and dispatches messages Win32 applications are responsible for message pumping, but this is easy to do because Win32 provides the functions that are necessary The standard message pump involves these three Win32 functions:
• GetMessage
• TranslateMessage
• DispatchMessage
The GetMessage function retrieves any messages that have been queued for the application If no
messages are present, GetMessage waits until a message arrives before returning This behavior isn’t well
suited for games—a subject we’ll return to later in this lesson
The TranslateMessage function is used to handle accelerators, the Windows term for “hot keys” or
keyboard shortcuts This feature allows keyboard commands to be mapped to menu commands
Finally, the DispatchMessage function instructs Win32 to deliver the message Messaging works on a window basis Each message is addressed to a window as represented by the HWND data type
The standard message pump looks like this:
Once a message has been retrieved, it is passed first to 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
Trang 13This message pump can be placed inside the WinMain function, like this:
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
However, since messages are addressed to specific windows, and we don’t have one yet, there’s no point
in compiling this code
Callback Functions
Software programs are all composed of functions and data In Lesson 2, when we learned about pointers,
we found that data resides in memory and can be accessed by memory address through the use of
pointers
This is also true of functions Like the data in a program, the series of instructions that are contained in functions reside in memory As such, functions have addresses, just like data items Normally it isn’t necessary to take the address of a function, or concern yourself with how function calls and definitions
actually work, but these subjects are necessary in order to use callback functions A callback function is
no different than a regular function, but how it gets called is important
Normally, with the exception of the main or WinMain functions, you are responsible for every function call in the program The program entry point function (main or WinMain) is called by the startup code,
but after that, every subsequent function call is made by the entry point function, either directly or
indirectly
A callback function is a function that is application defined, but called by system or library code The address of a callback function is provided to an API such as Win32, and the API implementation calls the function at a later time Callback functions allow a generic API to perform an application-specific task
By calling a callback function, the API is temporarily handing control over to the application for a
specific purpose
In the case of Windows messaging, callback functions are used to allow the application to respond to Windows messages The application defines a function that handles the messages of interest, and supplies Win32 with the address of this function Then, whenever a message is dispatched to the application, Window calls this function via the function pointer passing to it the message information
In order to understand how the address of a function can be determined, and how it can later be used to make a function 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 << val << endl;
Trang 14}
When we compile our program, the compiler converts this function into a set of instructions, and embeds these instructions into the final executable When the executable is launched, these instructions are loaded into memory at an address that is determined at run-time Each time the function is called, the execution flow is diverted to the address where the function happened to be loaded into memory
Let’s assume that Func happened to be loaded into memory at the address 0x5000 This means that when
Func is called, like this:
Func( 100 );
This function call diverts the execution flow to the memory address 0x5000 This is possible because
Func is actually a pointer—a pointer to a function The parentheses that follow each function are similar
to the de-reference operator: they access the function at the address indicated by the preceding function name In other words, a function name followed by an argument list is a function call, but a function name
by itself is the address where the function resides in memory:
cout << Func << endl; // displays 0x5000
This code displays the address of Func Taking the address of a function is easy—no “address of”
operator is required because the function name is a pointer This is actually all we need to know in order
to define a callback function, but since we’re on the subject, let’s take a look at how function pointers are declared and used
Declaring pointers to functions is not very intuitive because the return type and argument list are part of the data type Moreover, the resulting pointer name doesn’t appear after the data type, as is normally the case:
void (*pFuncPtr)(int);
This declares a pointer named pFuncPtr that is a pointer to a function that has a return type of void, and
takes a single integer argument This pointer can legally be assigned to any function that matches this description, like this:
pFuncPtr = Func;
The fact that this assignment compiles demonstrates that a function name is indeed a pointer Both
pFuncPtr and Func now contain the address where the code that implements the Func function resides
This means that either one can be used to call it, like this:
Func( 100 ); // calls Func with 100 as an argument
pFuncPtr( 200 ); // calls Func with 200 as an argument
Both of these calls result in the execution of the same code because they both point to the same function Win32 uses callback functions to deliver messages to each application Each window that an application creates is required to define a callback function and provide its address to Win32 In the example we used
previously, the function had to have a return type of void and accept an integer argument Win32 requires
that message handling callback functions have a return type and argument list that looks like this:
Trang 15LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam );
The callback function that we provide must have the return type and argument list shown, but can be named whatever we want It is the address of this function that we’ll provide to Win32, not the name The
name used here, WndProc, is arbitrary, but this is the name we’ll use in this lesson
Window Creation
The final item that we need to construct a windowed application is the window itself Because of how Win32 works, creating a window actually involves two steps: registering a window class, and creating the window
Win32 requires that in order for a new window to be created, a window class—or window type—be
defined This can be done with the Win32 RegisterClass function, which is declared like this:
ATOM RegisterClass( CONST WNDCLASS *lpWndClass );
The RegisterClass function takes the address of a variable of the WNDCLASS structure type This structure contains the properties and options for the new window class The WNDCLASS structure looks
Because our goal is to create a very simple window, we don’t need to use all of the members provided by
the WNDCLASS structure We can leave several of them assigned to zero to indicate default settings
An important WNDCLASS data member is the lpfnWndProc member, which is a pointer to the message
handling callback function that Windows will invoke each time a message is to be delivered to this window class
The RegisterClass function returns a data item called an ATOM As long was this value is non-zero, the call to RegisterClass has succeeded It is not necessary to save this value for later use For our purposes,
we’ll register a window class with this code:
WNDCLASS wc;
ZeroMemory( &wc, sizeof(wc) );
wc.style = CS_HREDRAW | CS_VREDRAW;
Trang 16initializer list, except that zero is the only value supported
The code above assigns the lpfnWndProc member of the WNDCLASS structure with WndProc This is
the name of the message handling callback function that we’ll define later
After a window class has been defined, the application is ready to create the actual window This is done
with the CreateWindow function, which is declared like this:
HWND CreateWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName,
DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HANDLE hInstance, PVOID lpParam );
The CreateWindow function takes 11 arguments, the first of which is most important This is a const
string containing the name of the window class This must be the same name previously provided to the
RegisterClass function (as the lpszClassName member of the WNDCLASS structure) Notice also that
the position and size of the new window are determined by the values passed as the x, y, nWidth, and
nHeight arguments
We’ll call CreateWindow like this:
hwnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
100, 100, 320, 240, NULL, NULL, hInst, NULL);
CreateWindow returns a window handle (an HWND) that represents the new window A handle value of
zero indicates that CreateWindow has failed
The SimpleWindow Sample
Now that we know what goes into a windowed application, we’re ready to put it all together into a
sample Actually, we’re going to write two versions of this sample First, we’ll write a version that does not use classes or inheritance Then, after a discussion of how classes can be used to improve upon this version, we’ll write a class-based version
As a function-based application, the SimpleWindow sample is broken into four functions:
• WinMain
• RegisterWindowClass
• CreateSimpleWindow
Trang 17• WndProc
The WinMain function is the application entry point function The RegisterWindowClass function performs the Win32 window type registration, and the CreateSimpleWindow function creates the actual window The WndProc function is the message-handling callback function
In addition to these four functions, this sample uses two global variables, declared like this:
HINSTANCE hInst = 0;
HWND hwnd = 0
The hInst variable is used to store the application handle provided to WinMain, and the hwnd variable
stores the window handle As global variables, these values are available to all of the functions in a program As we discussed in Lesson 2 using global variables isn’t an ideal solution, so we’ll find an alternative when we write a class-based version
We’ll start our look at the SimpleWindow sample with the first function that gets called: WinMain: int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
The WinMain function begins by assigning the global variable hInst with the value provided by
Windows as the hInstance parameter This makes the application handle available globally
Next the RegisterWindowClass function is called If this function fails and returns false, a message box
is displayed with an error message, and WinMain returns Otherwise, WinMain calls the
CreateSimpleWindow function, which creates the application window and displays it If this operation
fails, the problem is reported with a message box, and WinMain returns