
Beginning Visual C++ 2005 (2006) [eng]-1
.pdf
|
|
Windows Programming Concepts |
|
|
|
|
LPARAM |
A message parameter. |
|
LPCSTR |
A pointer to a constant null-terminated string of 8-bit characters. |
|
LPHANDLE |
A pointer to a handle. |
|
LRESULT |
A signed value that results from processing a message. |
|
WORD |
A 16-bit unsigned integer so it corresponds to type unsigned short in |
|
|
C++. |
|
|
|
I’ll introduce any other Windows types we are using in examples as the need arises. All the types used by Windows, as well as the prototypes of the Windows API functions, are contained in the header file windows.h, so you need to include this header file when you put your basic Windows program together.
Notation in Windows Programs
In many Windows programs, variable names have a prefix which indicates what kind of value the variable holds and how it is used. There are quite a few prefixes and they are often used in combination. For example, the prefix lpfn signifies a long pointer to a function. A sample of the prefixes you might come across is:
Prefix |
Meaning |
|
|
b |
a logical variable of type BOOL, equivalent to int |
by |
type unsigned char; a byte |
c |
type char |
dw |
type DWORD, which is unsigned long |
fn |
a function |
h |
a handle, used to reference something. |
i |
type int |
l |
type long |
lp |
long pointer |
n |
type int |
p |
a pointer |
s |
a string |
sz |
a zero terminated string |
w |
type WORD, which is unsigned short |
|
|
619

Chapter 11
This use of these prefixes is called Hungarian notation. It was introduced to minimize the possibility of misusing a variable by interpreting it differently from how it was defined or intended to be used. Such misinterpretation was easily done in the C language, a precursor of C++. With C++ and its stronger type checking you don’t need to make such a special effort with your notation to avoid such problems. The compiler always flags an error for type inconsistencies in your program, and many of the kinds of bugs that plagued earlier C programs can’t occur with C++.
On the other hand, Hungarian notation can still help to make programs easier to understand, particularly when you are dealing with a lot of variables of different types that are arguments to Windows API functions. Because Windows programs are still written in C, and of course because parameters for
Windows API functions are still defined using Hungarian notation, the method is still used quite widely. You can find out more about Hungarian notation at http://web.umr.edu/~cpp/common/hungarian
.html.
You can make up your own mind as to the extent to which you want to use Hungarian notation as it is by no means obligatory. You may choose not to use it at all, but in any event, if you have an idea of how it works, you will find it easier to understand what the arguments to the Windows API functions are. There is a small caveat, however. As Windows has developed, the types of some of the API function arguments have changed slightly, but the variable names that are used remain the same. As a consequence, the prefix may not be quite correct in specifying the variable type.
The Structure of a Windows Program
For a minimal Windows program that just uses the Windows API, you will write two functions. These are a WinMain() function, where execution of the program begins and basic program initialization is carried out, and a WindowProc() function that is called by Windows to process messages for the application. The WindowProc() part of a Windows program is usually the larger portion because this is where most of the application-specific code is, responding to messages caused by user input of one kind or another.
Although these two functions make up a complete program, they are not directly connected. WinMain() does not call WindowProc(), Windows does. In fact, Windows also calls WinMain(). This is illustrated in Figure 11-2.
The function WinMain() communicates with Windows by calling some of the Windows API functions. The same applies to WindowProc(). The integrating factor in your Windows program is Windows itself, which links to both WinMain() and WindowProc(). You will take a look at what the pieces are that make up WinMain() and WindowProc() and then assemble the parts into a working example of a simple Windows program.
620

Windows Programming Concepts
|
WINDOWS |
|
Windows API |
ProgramStart |
Messages |
WinMain() |
WindowProc() |
|
Your Program |
Figure 11-2 |
|
The WinMain() Function
The WinMain() function is the equivalent of the main() function in a console program. It’s where execution starts and where the basic initialization for the rest of the program is carried out. To allow Windows to pass data to it, WinMain() has four parameters and a return value of type int. Its prototype is:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow );
621

Chapter 11
Following the return type specifier, int, you have a specification for the function, WINAPI, which is new to you. This is a Windows-defined specifier that causes the function name and arguments to be handled in a special way, which happens to correspond to the way that a function is handled in the Pascal and Fortran languages. This is different from the way functions are normally handled in C++. The precise details are unimportant — this is simply the way Windows requires things to be, so you need to put the WINAPI specifier in front of the names of functions called by Windows.
The four arguments that are passed by Windows to your WinMain() function contain important data. The first argument, hInstance, is of type HINSTANCE which is a handle to an instance — an instance here being a running program. A handle is an integer value which identifies an object of some kind — in this case, the instance of the application. The actual integer value of a handle is not important. There can
be several programs in execution under Windows at any given instant. This raises the possibility of several copies of the same application being active at once, and this needs to be recognized. Hence, the hInstance handle identifies a particular copy. If you start more than one copy of a program, each one has its own unique hInstance value. As you will see shortly, handles are also used to identify all sorts of other things. Of course, all handles in a particular context — application instance handles for example — need to be different from one another.
The next argument passed to your WinMain() function, hPrevInstance, is a legacy from the 16-bit versions of the Windows operating system. Under Windows 3.x, this parameter gave you the handle to the previous instance of the program, if there was one. If hPrevInstance was NULL, you knew that there was no previous instance of the program, so this must be the only copy of the program executing (at the moment, anyway). This information was necessary in many cases, because programs running under Windows 3.x share the same address space and multiple copies of a program executing simultaneously could cause complications. For this reason, programmers often limited their applications to only one running instance at a time and having the hPrevInstance argument passed to WinMain() allowed them to provide for this very easily by testing it in an if statement.
Under 32-bit versions of Windows the hPrevInstance parameter is completely irrelevant because each application runs in its own address space, and one application has no direct knowledge of the existence of another that is executing concurrently. This parameter is always NULL, even if another instance of an application is running.
The next argument, lpCmdLine, is a pointer to a string containing the command line that started the program. For instance, if you started it using the Run command from the Start button menu of Windows, the string contains everything that appears in the Open box. Having this pointer allows you to pick up any parameter values that may appear in the command line. The type LPSTR is another Windows type, specifying a 32-bit (long) pointer to a string.
The last argument, nCmdShow, determines how the window is to look when it is created. It could be displayed normally or it might need to be minimized; for example, the shortcut for the program might specify that the program should be minimized when it starts. This argument can take one of a fixed set of values that are defined by symbolic constants such as SW_SHOWNORMAL and SW_SHOWMINNOACTIVE. There are a number of other constants like these that define the way a window is to be displayed and they all begin SW_. Other examples are SW_HIDE or SW_SHOWMAXIMIZED. You don’t usually need to examine the value of nCmdShow. You typically pass it directly to the Windows API function responsible for displaying your application window.
If you want to know what all the other constants are that specify how a window displays, you can find a complete list of the possible values if you search for WinMain in the MSDN Library. You can access the MSDN library online at http://msdn.microsoft.com.
622

Windows Programming Concepts
The function WinMain() in your Windows program needs to do four things:
Tell Windows what kind of window the program requires
Create the program window
Initialize the program window
Retrieve Windows messages intended for the program
Take a look at each of these in turn and then create a complete WinMain() function.
Specifying a Program Window
The first step in creating a window is to define just what sort of window it is that you want to create. Windows defines a special struct type called WNDCLASSEX to contain the data specifying a window. The data that is stored in an instance of the struct defines a window class, which determines the type of window. Do not confuse this with a C++ class — the MFC defines a class that represent a window but this is not the same this at all. You need to create a variable of type WNDCLASSEX, and give values to each of its members (just like filling in a form). After you’ve filled in the variables, you can pass it to Windows (via a function that you’ll see later) to register the class. When that’s been done, whenever you want to create a window of that class, you can tell Windows to look up the class that you’ve already registered.
The definition of the WNDCLASSEX structure is as follows:
struct WNDCLASS |
|
{ |
|
UINT cbSize; |
// Size of this object in bytes |
UINT style; |
// Window style |
WNDPROC lpfnWndProc; |
// Pointer to message processing function |
int cbClsExtra; |
// Extra byte after the window class |
int cbWndExtra; |
// Extra bytes after the window instance |
HINSTANCE hInstance; |
// The application instance handle |
HICON hIcon; |
// The application icon |
HCURSOR hCursor; |
// The window cursor |
HBRUSH hbrBackground; |
// The brush defining the background color |
LPCTSTR lpszMenuName; |
// A pointer to the name of the menu resource |
LPCTSTR lpszClassName; |
// A pointer to the class name |
HICON hIconSm; |
// A small icon associated with the window |
}; |
|
|
|
You construct an object of type WNDCLASSEX in the way that you saw when I discussed structures, for example:
WNDCLASSEX WindowClass; |
// Create a window class object |
All you need to do now is fill in values for the members of WindowClass.
Setting the value for the cbSize member of the struct is easy when you use the sizeof operator:
WindowClass.cbSize = sizeof(WNDCLASSEX);
623

Chapter 11
The style member of the struct determines various aspects of the window’s behavior, in particular, the conditions under which the window should be redrawn. You can select from a number of options for this member’s value, each defined by a symbolic constant beginning CS_.
You’ll find all the possible constant values for style if you search for WNDCLASSEX in the MSDN Library that you’ll find at http://msdn.microsoft.com.
Where two or more options are required, the constants can be combined to produce a composite value using the bitwise OR operator, |. For example:
WindowClass.style = CS_HREDRAW | CS_VREDRAW;
The option CS_HREDRAW indicates to Windows that the window is to be redrawn if its horizontal width is altered, and CS_VREDRAW indicates that it is to be redrawn if the vertical height of the window is changed. In the preceding statement you have elected to have the window redrawn in either case. As a result, Windows sends a message to your program indicating that you should redraw the window whenever the width or height of the window is altered by the user. Each of the possible options for the window style is defined by a unique bit in a 32-bit word being set to 1. That’s why the bitwise OR is used to combine them. These bits indicating a particular style are usually called flags. Flags are used very frequently, not only in Windows but also in C++ because they are an efficient way of representing and processing features that are either there or not or parameters that are either true or false.
The member lpfnWndProc stores a pointer to the function in your program that handle messages for the window you create. The prefix to the name signifies that this is a long pointer to a function. If you followed the herd and called the function to handle messages for the application WindowProc(), you would initialize this member with the statement:
WindowClass.lpfnWndProc = WindowProc;
The next two members, cbClsExtra and cbWndExtra, allow you to ask that extra space be provided internally to Windows for your own use. An example of this could be when you want to associate additional data with each instance of a window to assist in message handling for each window instance. Normally you won’t need extra space allocated for you, in which case you must set the cbClsExtra and cbWndExtra members to zero.
The hInstance member holds the handle for the current application instance, so you should set this to the hInstance value that was passed to WinMain() by Windows.
The members hIcon, hCursor, and hbrBackground are handles that in turn define the icon that represents the application when minimized, the cursor the window uses, and the background color of the client area of the window. (As you saw earlier, a handle is just a 32-bit integer used as an ID to represent something.) These are set using Windows API functions. For example:
WindowClass.hIcon = LoadIcon(0, IDI_APPLICATION);
WindowClass.hCursor = LoadCursor(0, IDC_ARROW);
WindowClass.hbrBackground =
static_cast<HBRUSH>(GetStockObject(GRAY_BRUSH));
All three members are set to standard Windows values by these function calls. The icon is a default provided by Windows and the cursor is the standard arrow cursor used by the majority of Windows applications. A
624

Windows Programming Concepts
brush is a Windows object used to fill an area, in this case the client area of the window. The function GetStockObject() returns a generic type for all stock objects, so you need to cast it to type HBRUSH. In the preceding example it returns a handle to the standard gray brush, and the background color for our window is thus set to gray. This function can also be used to obtain other standard objects for a window, such as fonts for example. You could also set the hIcon and hCursor members to null, in which case Windows would provide the default icon and cursor. If you set hbrBackground to null, your program expects to paint the window background and messages are sent to your application whenever this becomes necessary.
The lpszMenuName member is set to the name of a resource defining the window menu, or to zero if there is no menu for the window. You will look into creating and using menu resources when you use the AppWizard.
The lpszClassName member of the struct stores the name that you supply to identify this particular class of window. You would usually use the name of the application for this. You need to keep track of this name because you will need it again when a window is created. This member would therefore be typically set with the statements:
static char szAppName[] = |
“OFWin”; |
// |
Define window class name |
WindowClass.lpszClassName |
= szAppName; |
// |
Set class name |
The last member is hIconSm, which identifies a small icon associated with the window class. If you specify this as null, Windows searches for a small icon related to the hIcon member and use that.
In fact, the WNDCLASSEX structure replaces another structure, WNDCLASS that was used for the same purpose. The old structure did not include the cbSize member that stores the size of the structure in bytes or the hIconSm member.
Creating a Program Window
After all the members of your WNDCLASSEX structure have been set to the values required, the next step is to tell Windows about it. You do this using the Windows API function RegisterClassEx(). Given that your structure is WindowClass, the statement to do this would be:
RegisterClassEx(&WindowClass);
Easy, isn’t it? The address of the struct is passed to the function, and Windows extracts and squirrels away all the values that you have set in the structure members. This process is called registering the window class. Just to remind you, the term class here is used in the sense of classification and is not the same as the idea of a class in C++, so don’t confuse the two. Each instance of the application must make sure that it registers the window classes that it needs. If you were using the obsolete WNDCLASS structure that I mentioned, you would have to use a different function here, RegisterClass().
After Windows knows the characteristics of the window that you want, and the function that is going to handle messages for it, you can go ahead and create it. You use the function CreateWindow() for this. The window class that you’ve already created determines the broad characteristics of a window, and further arguments to the function CreateWindow() add additional characteristics. Because an application may have several windows in general, the function CreateWindow() returns a handle to the window created that you can store to enable you to refer to that particular window later. There are many API calls that require you to specify the window handle as a parameter if you want to use them. You will look at a typical use of the CreateWindow() function at this point. This might be:
625

Chapter 11
HWND hWnd; |
// Window handle |
... |
|
|
|
hWnd = CreateWindow( |
|
szAppName, |
// the window class name |
“A Basic Window the Hard Way”, |
// The window title |
WS_OVERLAPPEDWINDOW, |
// Window style as overlapped |
CW_USEDEFAULT, |
// Default screen position of upper left |
CW_USEDEFAULT, |
// corner of our window as x,y... |
CW_USEDEFAULT, |
// Default window size, width... |
CW_USEDEFAULT, |
// ...and height |
0, |
// No parent window |
0, |
// No menu |
hInstance, |
// Program Instance handle |
0 |
// No window creation data |
); |
|
|
|
The variable hWnd of type HWND is a 32-bit integer handle to a window. You’ll use this variable to record the value that the CreateWindow() function returns that identifies the window. The first argument that you pass to the function is the class name. This is used by Windows to identify the WNDCLASSEX struct that you passed to it previously, in the RegisterClassEx() function call, so that the information from this struct can be used in the window creation process.
The second argument to CreateWindow() defines the text that is to appear on the title bar. The third argument specifies the style that the window has after it is created. The option specified here, WS_OVERLAPPEDWINDOW, actually combines several options. It defines the window as having the
WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX and WS_MAXIMIZEBOX styles. This results in an overlapped window, which is a window intended to be the main application window, with a title bar and a thick frame, which has a title bar icon, system menu, and maximize and minimize buttons. A window that you specify as having a thick frame has borders that can be resized.
The next four arguments determine the position and size of the window on the screen. The first two are the screen coordinates of the upper-left corner of the window, and the second two define the width and height of the window. The value CW_USEDEFAULT indicates that you want Windows to assign the default position and size for the window. This tells Windows to arrange successive windows in cascading positions down the screen. CW_USEDEFAULT only applies to windows specified as WS_OVERLAPPED.
The next argument value is zero, indicating that the window being created is not a child window (a window that is dependent on a parent window). If you wanted it to be a child window, you would set this argument to the handle of the parent window. The next argument is also zero, indicating that no menu is required. You then specify the handle of the current instance of the program which was passed to the program by Windows. The last argument for window creation data is zero because you just want a simple window in the example. If you wanted to create a multiple-document interface (MDI) client window, the last argument would point to a structure related to this. You’ll learn more about MDI windows later in the book.
Note that the Windows API also includes a CreateWindowEx() function that you use to create a window with extended style information.
626

Windows Programming Concepts
After calling the CreateWindow() function, the window now exists but is not yet displayed on the screen. You need to call another Windows API function to get it displayed:
ShowWindow(hWnd, nCmdShow); |
// Display the window |
Only two arguments are required here. The first identifies the window and is the handle returned by the function CreateWindow(). The second is the value nCmdShow that was passed to WinMain(), and that indicates how the window is to appear onscreen.
Initializing the Program Window
After calling the function ShowWindow(), the window appears onscreen but still has no application content, so you need to get your program to draw in the client area of the window. You could just put together some code to do this directly in the WinMain() function, but this would be most unsatisfactory: in this case, the contents of the client area are not considered to be permanent — if you want the client area contents to be retained, you can’t afford to output what you want and forget about it. Any action on the part of the user that modifies the window in some way, such as dragging a border or dragging the whole window, typically requires that the window and its client area are redrawn.
When the client area needs to be redrawn for any reason, Windows sends a particular message to your program and your WindowProc() function needs to respond by reconstructing the client area of the window. Therefore, the best way to get the client area drawn in the first instance is to put the code to draw the client area in the WindowProc() function and get Windows to send the message requesting that the client area be redrawn to your program. Whenever you know in your program that the window should be redrawn (when you change something, for example), you need to tell Windows to send a message back to get the window redrawn.
You can ask Windows to send your program a message to redraw the client are of the window by calling another Windows API function, UpdateWindow(). The statement to accomplish this is:
UpdateWindow(hWnd); |
// Cause window client area to be drawn |
This function requires only one argument: the window handle hWnd, which identifies your particular program window. (In general there can be several windows in an application.) The result of the call is that Windows sends a message to your program requesting that the client area be redrawn.
Dealing with Windows Messages
The last task that WinMain() needs to address is dealing with the messages that Windows may have queued for your application. This may seem a bit odd because I said earlier that you needed the function WindowProc() to deal with messages, but let me explain a little further.
Queued and Non-Queued Messages
I oversimplified Windows messaging when I introduced the idea earlier. There are, in fact, two kinds of Windows messages:
There are queued messages that Windows places in a queue, and the WinMain() function must extract these messages from the queue for processing. The code in WinMain() that does this is called the message loop. Queued messages include those arising from user input from the keyboard, moving the mouse and clicking the mouse buttons. Messages from a timer and the Windows message to request that a window be redrawn are also queued.
627

Chapter 11
There are non-queued messages that result in the WindowProc() function being called directly by Windows. A lot of the non-queued messages arise as a consequence of processing queued messages. What you are doing in the message loop in WinMain() is retrieving a message that Windows has queued for your application and then asking Windows to invoke your WindowProc() function to process it. Why can’t Windows just call WindowProc() whenever necessary? Well, it could, but it just doesn’t work this way. The reasons have to do with how Windows manages multiple applications executing simultaneously.
The Message Loop
As I said, retrieving messages from the message queue is done using a standard mechanism in Windows programming called the message pump or message loop. The code for this would be:
MSG msg; |
// Windows message structure |
while(GetMessage(&msg, 0, 0, 0) == TRUE) |
// Get any messages |
{ |
|
TranslateMessage(&msg); |
// Translate the message |
DispatchMessage(&msg); |
// Dispatch the message |
} |
|
|
|
This involves three steps in dealing with each message:
GetMessage() — retrieves a message from the queue.
TranslateMessage() — performs any conversion necessary on the message retrieved.
DispatchMessage() — causes Windows to call the WindowProc() function in your application to deal with the message.
The operation of GetMessage() is important because it has a significant contribution to the way Windows works with multiple applications. Take a look at it in a little more detail.
The GetMessage() function retrieves a message queued for the application window and stores information about the message in the variable msg, pointed to by the first argument. The variable msg, which is a struct of type MSG, contains a number of different members that you are not accessing here. Still, for completeness, the definition of the structure looks like this:
struct MSG |
|
|
{ |
|
|
HWND |
hwnd; |
// Handle for the relevant window |
UINT |
message; |
// The message ID |
WPARAM |
wParam; |
// Message parameter (32-bits) |
LPARAM |
lParam; |
// Message parameter (32-bits) |
DWORD |
time; |
// The time when the message was queued |
POINT |
pt; |
// The mouse position |
}; |
|
|
|
|
|
The wParam member is an example of a slightly misleading Hungarian notation prefix that I mentioned was now possible. You might assume that it was of type WORD (which is int), which used to be true in earlier Windows versions, but now it is of type WPARAM, which is a 32-bit integer value.
628