|VC++ 6.0 ebook chapter index|
Even with my explanation of HELLOWIN, the structure and workings of the program are probably still quite mysterious. In a short C program written for a character-mode environment, the entire program might be contained in the main function. In HELLOWIN, WinMain contains only program overhead necessary to register the window class, create the window, and retrieve and dispatch messages from the message queue.
All the real action of the program occurs in the window procedure. In HELLOWIN, this action is not much—WndProc simply plays a sound file and displays a text string in its window. But in later chapters, you'll find that almost everything a Windows program does is in response to a message to a window procedure. This is one of the major conceptual hurdles you must leap to begin writing Windows programs.
Don't Call Me, I'll Call You
Programmers are well acquainted with the idea of calling on the operating system to do something. For example, C programmers use the fopen function to open a file. The fopen function is implemented with a call to the operating system to open a file. No problem.
But Windows is different. Although Windows has a couple thousand function calls, Windows also makes calls to your program, specifically to the window procedure we have called WndProc. The window procedure is associated with a window class that the program registers by calling RegisterClass. A window that is created based on this window class uses this window procedure for processing all messages to the window. Windows sends a message to the window by calling the window procedure.
Windows calls WndProc when a window is first created. Windows calls WndProc when the window is eventually destroyed. Windows calls WndProc when the window has been resized or moved or minimized. Windows calls WndProc when a user clicks on the window with the mouse. Windows calls WndProc when characters are typed from the keyboard. Windows calls WndProc when an item has been selected from a menu. Windows calls WndProc when a scroll bar is manipulated or clicked with the mouse. Windows calls WndProc to tell it when it must repaint its client area.
All these calls to WndProc are in the form of messages. In most Windows programs, the bulk of the program is dedicated to handling these messages. The messages that Windows can send to a program are generally identified with names that begin with the letters WM and are defined in the WINUSER.H header file.
Actually, the idea of a routine within a program that is called from outside the program is not unheard of in character-mode programming. The signal function in C can trap a Ctrl-C break or other interrupts from the operating system. Old programs written for MS-DOS often trapped hardware interrupts.
But in Windows this concept is extended to cover everything. Everything that happens to a window is relayed to the window procedure in the form of a message. The window procedure then responds to this message in some way or passes the message to DefWindowProc for default processing.
The wParam and lParam parameters to the window procedure are not used in HELLOWIN except as parameters to DefWindowProc. These parameters give the window procedure additional information about the message. The meaning of the parameters is message-dependent.
Let's look at an example. Whenever the client area of a window changes in size, Windows calls that window's window procedure. The hwnd parameter to the window procedure is the handle of the window changing in size. (Remember that one window procedure could be handling messages for multiple windows that were created based on the same window class. The hwnd parameter lets the window procedure know which window is receiving the message.) The message parameter is WM_SIZE. The wParam parameter for a WM_SIZE message is the value SIZE_RESTORED, SIZE_MINIMIZED, SIZE_MAXIMIZED, SIZE_MAXSHOW, or SIZE_MAXHIDE (defined in the WINUSER.H header file as the numbers 0 through 4). That is, the wParam parameter indicates whether the window is being changed to a nonminimized or nonmaximized size, being minimized, being maximized, or being hidden.
The lParam parameter contains the new size of the window. The new width (a 16-bit value) and the new height (a 16-bit value) are stuck together in the 32-bit lParam. The WINDEF.H header file defines some handy macros that help you extract these two values from lParam. We'll do this in the next chapter.
Sometimes messages generate other messages as a result of DefWindowProc processing. For example, suppose you run HELLOWIN and you eventually click the Close button, or suppose you select Close from the system menu using either the keyboard or the mouse. DefWindowProc processes this keyboard or mouse input. When it detects that you have selected the Close option, it sends a WM_SYSCOMMAND message to the window procedure. WndProc passes this message to DefWindowProc. DefWindowProc responds by sending a WM_CLOSE message to the window procedure. WndProc again passes this message to DefWindowProc. DefWindowProc responds to the WM_CLOSE message by calling DestroyWindow. DestroyWindow causes Windows to send a WM_DESTROY message to the window procedure. WndProc finally responds to this message by calling PostQuitMessage to put a WM_QUIT message in the message queue. This message causes the message loop in WinMain to terminate and the program to end.
Queued and Nonqueued Messages
I've talked about Windows sending messages to a window, which means that Windows calls the window procedure. But a Windows program also has a message loop that retrieves messages from a message queue by calling GetMessage and dispatches these messages to the window procedure by calling DispatchMessage.
So, does a Windows program poll for messages (much like a character-mode program polling for keyboard input) and then route these messages to some location? Or does it receive messages directly from outside the program? Well, both.
Messages can be either "queued" or "nonqueued." The queued messages are those that are placed in a program's message queue by Windows. In the program's message loop, the messages are retrieved and dispatched to the window procedure. The nonqueued messages are the results of calls by Windows directly to the window procedure. It is said that queued messages are "posted" to a message queue and that nonqueued messages are "sent" to the window procedure. In any case, the window procedure gets all the messages—both queued and nonqueued—for the window. The window procedure is "message central" for the window.
The queued messages are primarily those that result from user input in the form of keystrokes (such as the WM_KEYDOWN and WM_KEYUP messages), characters that result from keystrokes (WM_CHAR), mouse movement (WM_MOUSEMOVE), and mouse-button clicks (WM_LBUTTONDOWN). Queued messages also include the timer message (WM_TIMER), the repaint message (WM_PAINT), and the quit message (WM_QUIT).
The nonqueued messages are everything else. Nonqueued messages often result from calling certain Windows functions. For example, when WinMain calls CreateWindow, Windows creates the window and in the process sends the window procedure a WM_CREATE message. When WinMain calls ShowWindow, Windows sends the window procedure WM_SIZE and WM_SHOWWINDOW messages. When WinMain calls UpdateWindow, Windows sends the window procedure a WM_PAINT message. Queued messages signaling keyboard or mouse input can also result in nonqueued messages. For example, when you select a menu item with the keyboard or mouse, the keyboard or mouse message is queued but the eventual WM_COMMAND message indicating that a menu item has been selected is nonqueued.
This process is obviously complex, but fortunately most of the complexity is Windows' problem rather than our program's. From the perspective of the window procedure, these messages come through in an orderly and synchronized manner. The window procedure can do something with these messages or ignore them.
When I say that messages come through in an orderly and synchronized manner, I mean first that messages are not like hardware interrupts. While processing one message in a window procedure, the program will not be suddenly interrupted by another message.
Although Windows programs can have multiple threads of execution, each thread's message queue handles messages for only the windows whose window procedures are executed in that thread. In other words, the message loop and the window procedure do not run concurrently. When a message loop retrieves a message from its message queue and calls DispatchMessage to send the message off to the window procedure, DispatchMessage does not return until the window procedure has returned control back to Windows.
However, the window procedure could call a function that sends the window procedure another message, in which case the window procedure must finish processing the second message before the function call returns, at which time the window procedure proceeds with the original message. For example, when a window procedure calls UpdateWindow, Windows calls the window procedure with a WM_PAINT message. When the window procedure finishes processing the WM_PAINT message, the UpdateWindow call will return controls back to the window procedure.
This means that window procedures must be reentrant. In most cases, this doesn't cause problems, but you should be aware of it. For example, suppose you set a static variable in the window procedure while processing a message and then you call a Windows function. Upon return from that function, can you be assured that the variable is still the same? Not necessarily—not if the particular Windows function you call generated another message and the window procedure changes the variable while processing that second message. This is one of the reasons why certain forms of compiler optimization must be turned off when compiling Windows programs.
In many cases, the window procedure must retain information it obtains in one message and use it while processing another message. This information must be saved in variables defined as static in the window procedure, or saved in global variables.
Of course, you'll get a much better feel for all of this in later chapters as the window procedures are expanded to process more messages.
Get In and Out Fast
Windows 98 and Windows NT are preemptive multitasking environments. This means that as one program is doing a lengthy job, Windows can allow the user to switch control to another program. This is a good thing, and it is one advantage of the current versions of Windows over the older 16-bit versions.
However, because of the way that Windows is structured, this preemptive multitasking does not always work the way you might like. For example, suppose your program spends a minute or two processing a particular message. Yes, the user can switch to another program. But the user cannot do anything with your program. The user cannot move your program's window, resize it, minimize it, close it, nothing. That's because your window procedure is busy doing a lengthy job. Oh, it may not seem like the window procedure performs its own moving and sizing operations, but it does. That's part of the job of DefWindowProc, which must be considered as part of your window procedure.
If your program needs to perform lengthy jobs while processing particular messages, there are ways to do so politely that I'll describe in Chapter 20. Even with preemptive multitasking, it's not a good idea to leave your window sitting inert on the screen. It annoys users. It annoys users just as much as bugs, nonstandard behavior, and incomplete help files. Give the user a break, and return quickly from all messages.