Marc Adler
The Microsoft WindowsÔ graphical environment has been getting a lot of attention lately. Perhaps you're interested in programming for Windows1 but don't know how or where to start, or you're unconvinced that Windows is the way to go. MSJ will introduce you to Windows programming with a series of articles starting in this issue, featuring brand-new Windows Version 3.0. This article will develop a small but useful Windows application, examining each component as it is added.
Why is Windows in the spotlight so much these days? In the past, graphical user interfaces (GUIs) such as Windows were the domain of expensive computer systems with more CPU power than the average person could afford. GUIs were certainly out of the question on the original 4.77 MHz PC. The introduction of the IBM PC/AT in 1984 and the EGA video standard made graphical systems more feasible, but the screen drawing speed was still sluggish enough to dissuade people from using GUIs. Today, a 386Ô running at 16 MHz with a VGA card, color monitor, and a mouse seems to be standard in much of the corporate world as well as among many developers. As hardware prices fall, it seems likely that graphical 386/486Ô systems will become even more widespread.
As affordable computers capable of handling this type of system proliferate, many predict that GUIs will become the standard user interface. The intuitive, object-oriented, icon-based method will entice more and more computer-wary people into using the computer as an everyday tool.
Most major computer companies realize this and are developing their products accordingly. IBM has created a blueprint for all of its future applications called Systems Application ArchitectureÔ (SAAÔ). The part of SAA governing how the user interacts with the program is called the Common User Access (CUA). Adherence to CUA standards ensures that operating methods remain consistent among a wide variety of products, thus lessening the amount of time it takes a new user to become proficient with an application. For example, pressing F1 in a CUA-compliant application invokes context-sensitive help regardless of the user's location in the application. The user also knows that help screens will be in a certain format and that they will lead to an index of commands and a table of command keystrokes. Consistency among applications reduces the substantial cost of training employees on new software.
Windows is fully compliant with CUA and will continue to be, which is one attractive reason to program in it. Another reason is device-independence. Properly written Windows applications run on a wide variety of hardware, from monochrome monitors to Super-VGA adapters hooked up to 19" displays. The device-independence even extends as far as printing. As long as Windows supports your printer (and it supports a lot of them), you will be able to print a document with embedded graphics without having to worry about printer-dependent code. Other reasons to program in Windows include its ability to multitask applications; its sophisticated memory management, with multitasking among large applications; and its ability to take advantage of protected mode automatically on a 386-based system.
Sample Application
The sample Windows application that will be built is a small stock portfolio analysis program. Not only will it demonstrate each of the elements of a Windows program, it's something that might be of use to you. You'll learn how to design and create the application's pull-down, multilevel menu system. Several dialog boxes with different kinds of control windows will collect data from the user regarding the stocks as well as the daily trading information for each stock. You will perform subclassing of existing windows classes when you implement the dialog boxes and eventually may experiment with the new custom control facility of Windows 3.0.
The sample application will be able to display several different types of charts, including a line graph of a stock's price over a period of time and a bar chart of the stock's daily volume. Since the user will probably want hardcopy of the graphs, you'll also see how printing is done under Windows.
Not only does the stock application permit exploration of important features of Windows, it also provides a base for enhancements. For example, the ability to download stock data at the end of the day from a commercial online database, such as Dow Jones or CompuServe, would be a useful feature. This would entail exploring the communications routines in Windows.
If you think of each stock as being a separate document, you can experiment with the Windows Multiple Document Interface (MDI). Windows now has a built-in API for creating an MDI-based application. (For more on MDI, see "A New Multiple Document Interface API Simplifies MDI Application Development" p. 53-Ed.) Finally, you will set up a Dynamic Data Exchange (DDE) conversation with a Microsoft Excel spreadsheet.
Windows Programming Paradigm
Let's begin by discussing the Windows programming model. The single most important adjustment you have to make to go from standard C programming to Windows programming is the realization that all Windows applications are based on the concept of message passing. Every Windows program requires a message loop and a message handler for each window in the application.
Messages are generated by Windows whenever an event occurs or when an action needs to be taken. When you move the mouse, Windows generates a message that tells your application that the mouse has moved; embedded in the message are the coordinates of the point the mouse has moved to. When you choose something from a menu, Windows informs your application that an item was selected via a message. Windows will frequently send your application messages when "interesting" things happen, such as when a window needs to be redrawn. The essence of programming a Windows application is to fetch a message, direct that message to the window the message is meant for, act as quickly as you can to process that message, and go back and read another message.
Why have a message loop? Why send messages at all? Windows is a multitasking system, so multiple programs will be running at the same time. Because each program requires CPU time, no one program should be able to hog the CPU for a substantial period of time. If a program uses the CPU for a long period, other time-critical applications running simultaneously may fail to work properly. Preemptive multitasking systems such as UNIX will interrupt an application when its allocated time slice expires and pass control of the CPU to another application, even if the first application is in the middle of an important operation. Windows multitasking is nonpreemptive. An application must explicitly yield control to another application-Windows will not preempt a task on its own. Windows does its multitasking whenever an application examines the message queue. If there are no pending messages for an application, Windows will check for messages meant for other running applications. If another application has a message pending, Windows will give control to that application. That application in turn will fetch its message, act on it, then try to read another message. At this point, Windows resumes the cycle.
You must structure your Windows application to cooperate with this "pass-the-baton" method of operation. If you don't, your application might still run correctly, but other applications executing at the same time probably will not run. In other words, you can't use getc's any more!
Messages
Let's examine a message. In Windows, all messages are in a fixed, compact format that facilitates rapid message processing. If you look at the huge WINDOWS.H include file that comes with the Windows Software Development Kit (SDK), you can find the exact structure of a message (see Figure 1).
The first member of the structure, hwnd, tells which window the message is targeted for. Each window has a number associated with it, known as a window handle, that uniquely identifies the window in a Windows session.
The second member, message, is the message identifier. It tells the application exactly what event has occurred. A list of all of the messages that Windows knows about can also be found in WINDOWS.H (see Figure 2). All message names start with WM_ followed by a descriptive name such as WM_PAINT, WM_CHAR, or WM_COMMAND.
The third and fourth members, wParam and lParam, contain additional information about the message. Their contents are dependent on the message. A complete listing of the messages and their parameters can be found in the SDK Programmer's Reference.
The last two members of the message structure, while always present, are not used as frequently as the other members. The first of these, "time," is the time at which the message was placed into the message queue; "pt" contains the coordinates of the mouse at the time that the message was placed into the queue.
When a message is generated by an input event, Windows places it in the system message queue. Windows maintains a single system message queue that is common to all applications. The system message queue is used to store all events generated by the keyboard and the mouse (see Figure 3).
Each application also has its own message queue. Windows places application-specific messages directly into the application queue, entirely bypassing the system queue.
Writing the Message Loop
A sample Windows message loop, as it appears in many Windows applications, is shown below.
MSG msg;
o
o
o
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Windows handles messages in the following sequence: when GetMessage is called, Windows looks first in the application's message queue. If it finds no messages, it scans the system queue for keyboard and mouse events. If it still finds no messages, it checks for timer events. If there are none, the application is put to sleep.
The GetMessage function suspends the application until a message arrives in its individual message queue, at which point it removes the message from the head of the queue and resumes the application. As mentioned previously, while the application is suspended, Windows gives other applications their turn with the CPU.
GetMessage gives an application the option to retrieve all messages, a message meant for a specific window within the application, or a message that falls within a particular range of messages. Most of the time, you want to retrieve any message that is meant for your application. I'll cover the details of GetMessage's parameters later.
GetMessage returns FALSE if the message received is a WM_QUIT message. When your application receives the WM_QUIT message, it means that your application wants to exit. A return value of FALSE should break the application out of the message loop.
The second function in the message loop is TranslateMessage. When the user presses a key, Windows dissects the keypress into key-up and key-down events. Windows then translates these two events into a message that represents a single keypress event. TranslateMessage operates only on keyboard messages; it translates the up/down events into one of four messages: WM_CHAR, WM_DEADCHAR, WM_SYSCHAR, or WM_SYSDEADCHAR.
Most applications can ignore the deadkey messages. The system character messages are generated when the user presses Alt with another key. Alt-key combinations usually signal Windows to take some sort of action, such as activating a menu or switching the active window to another application. All other keys will generate a WM_CHAR message.
The last function used in the message loop of most Windows applications is DispatchMessage. This function merely sends the message to the window the message was meant for. The message is actually sent to the message handler for that window, which is known as the window procedure. (Experienced programmers will notice that the TranslateAccelerator function is omitted. This will come up in a later article when the accelerator keys are added to the application.)
Posting versus Sending Messages
Windows is not the only place where messages can originate. Any application can source a message,which it can send to itself or to any other application currently running. Two very different methods are used to pass messages to a window: posting and sending.
When you post a message to a window, you are placing that message in the application queue, with the hwnd field of the message structure set to the target window's handle. If there are other messages already in the application queue, the window will not receive that message until the application processes the messages ahead of it. Yes, the window will eventually receive that message, but it could be a long time.
When you send a message to a window, you are in essence calling its window procedure directly. The message completely bypasses the application queue. You send a message to a window when you want that window to act on something immediately.
The two functions used to pass messages to a window are PostMessage and SendMessage. Their parameters are listed below.
PostMessage(HWND hWnd, WORD message, WORD wParam,
DWORD lParam);
SendMessage(HWND hWnd, WORD message, WORD wParam,
DWORD lParam);
Note that the functions' parameters are identical to the first four members of the MSG structure (see Figure 1).You can pass Windows-defined messages or you can create your own messages to send. If you examine the list of messages defined in the WINDOWS.H file, you'll notice a symbol called WM_USER. Windows reserves all values under WM_USER for its own use; all values above WM_USER can be used by your application. You can define a new message thus:
#define WM_MYMSG (WM_USER + 0x10)
Now that you know the basics of messaging, the main object in Windows can be discussed-the window!
Windows and WinProcs
Every window that you create under Microsoft Windows has a message handler associated with it. This message handler is referred to as a window procedure or WinProc. When a message is sent to a window using SendMessage, or when DispatchMessage passes a message to a window, the WinProc of a window is actually being called. A WinProc must be declared like this:
LONG FAR PASCAL WindowProc(hWnd, msg, wParam, lParam)
HWND hWnd;
WORD msg;
WORD wParam;
LONG lParam;
Again, the four parameters of a window procedure correspond to the first four fields of the MSG structure.
The Pascal calling convention used in this declaration is a relatively recent Microsoft innovation that makes the size of the compiled code slightly smaller. Two things distinguish a function using this calling convention from a function using the C calling convention: first, the arguments are placed on the stack from left to right, as opposed to right to left. Second, all stack maintenance is done by the function being called, not by the caller of the function as in the C calling convention (see Figure 4). If a function is called many times with the C calling convention, the same stack maintenance code must appear in the caller's code every time this function is called. This can add up to quite a bit of overhead. If the stack maintenance is done once and only once in the callee's code, the compiler no longer has to generate the stack maintenance code for each invocation of the function, saving code space and execution time. Microsoft estimates that using the Pascal calling convention reduced the code size of Windows by about 7 percent.
The WinProc receives every message sent to a window. You can choose to handle a message by writing some code to react to that message, or you can pass it on to the default window procedure.
The default window procedure, DefWindowProc, can handle all of the messages that can possibly be sent to a window. This WinProc has default processing for every Windows message. The Windows SDK even contains a file called DEFWND.C that lists the source code for DefWindowProc. (Note: DefWindowProc does not handle the Windows-defined control classes, each of which has its own internal WinProc.) If your WinProc does not handle a message, it must pass the message on to DefWindowProc.
A skeleton of a typical WinProc is shown in Figure 5. As you can see, this WinProc handles the WM_PAINT and the WM_DESTROY messages that are sent to the window and passes all other messages on to the default window procedure for default processing. If you forget to pass these messages on, your window would certainly not behave correctly.
Window Classes
Every window you create in a Windows program must belong to a window class. Before you create a window, you must make sure that its class is registered. Informally, a window class defines a window's behavior and its appearance. Windows belonging to the scroll bar class look and behave in a certain way, while windows of the button class behave entirely differently.
Formally, a window class is defined by the contents of a WNDCLASS data structure (see Figure 6). The class structure contains, among other elements, the name of the class, the name of the menu that will be attached to each window of that class, the number of extra bytes that will be allocated to each window of that class, the style of each window, and most importantly, the WinProc for each window of that class. By processing certain messages sent to the WinProc of a given class, you can define the behavior of each window created of that class.
When you register a class, you fill a WNDCLASS structure with the desired information and call the RegisterClass function. Once an application registers a window class, the class is available to any copy of the same application that is running. In previous versions of Windows, when you registered a window class, it was available to any application that ran during that Windows session.
Initialization Code
The message-passing loop, window classes, and window procedures have been discussed. Now it's time to begin writing the initialization code for the application.
Every Windows program must have a function called WinMain that serves as the entry point to the application. This is analogous to the main routine of a non-Windows program. However, the arguments to WinMain are very different.
WinMain looks like this :
int PASCAL WinMain(hInstance, hPrevInstance,
lpCmdLine, nCmdShow)
HANDLE hInstance;
HANDLE hPrevInstance;
LPSTR lpCmdLine;
int nCmdShow;
The instance identifier is hInstance. Under Windows, you can have multiple copies (called instances) of an application running at the same time. This does not mean, however, that you have multiple copies of the executable file in memory. Because Windows does not allow you to write into a code segment, the same code image remains unchanged for all copies of a single application. If there are multiple copies of an application running, Windows will keep only one copy of the code segment in memory. However, Windows will give each copy its own data and stack segments. The instance handle serves as a unique program identifier, so you can identify which copy of an application is running by examining the instance handle.
hPrevInstance is the instance handle of the previously invoked copy of an application. The first time you run an application, hPrevInstance will be NULL. You only need the handle of the previous instance to see if you need to do application-specific initialization. You need to register your application's window classes only once, no matter how many instances of the application are invoked. (Remember that when you register a window class, it is available to all instances of that application, so it's redundant to register that class again.)
lpCmdLine is a null-terminated string representing the command line with which the user invoked the application. In non-Windows C programs, the command line is parsed by the C start-up code; a pointer to each token is placed in the argv array for you. Windows does not pass the argv array to the WinMain, but you can use the following variables if you are programming in C:
extern int _argc; /* argument count */
extern char **_argv; /* array of arguments */
extern char **_envp; /* environment strings */
The final parameter, nCmdShow, specifies how the application will be initially displayed; that is, as a running, visible window or as an icon. It should be used only once in your WinMain function, as the second argument to the ShowWindow function. But more on this later.
At this point, you can write code to initialize the application (see Figure 7). Every window that is created using class MainWindowClass will use MainWindowProc as its window procedure. I have also specified the icon to use to represent the application when it is in the minimized state, the kind of mouse pointer or cursor desired, the brush that Windows will use to paint the window's background, the name of the menu that will be attached to the window, and the number of bytes in the per-window and per-class data structures.
A brush is simply a bitmap of a pattern that Windows uses to paint with. Windows has several predefined stock brushes; I will use a solid white "pattern" to paint the background of the main window.
Now that you have registered the main window class, create the main window. You must use the CreateWindow function to create any kind of window (see Figure 8).
Among the arguments CreateWindow expects is the name of the class the window belongs to, the string that will be used as the window's title, the style of the window, the location of the upper-left corner of the window, the dimensions of the window, and a handle to the parent of the window, if any.
I use a strange value for the upper-left corner coordinates, the width, and the height of the window. By specifying CW_USEDEFAULT, the Windows desktop manager decides where to place the main window and how big it should be. This is another example of device-independence in Windows; you don't have to specify the actual physical coordinates of the corners of the window.
The main window has been created, and will now be displayed. When you create a window using CreateWindow, the window will not be initially displayed. You must call ShowWindow to make the window appear.
ShowWindow(hWndMain, nCmdShow);
UpdateWindow(hWndMain);
For the very first invocation of ShowWindow (that is, the call to display your main window), you must give nCmdShow as the second parameter. The UpdateWindow function sends a WM_PAINT message to your main window, telling it that it must redraw the contents of its client area. In the sample WinProc (see Figure 9), you ignore the WM_PAINT message, letting the default WinProc handle the drawing.
The last thing to do in the main routine is enter the famous message loop. This loop will fetch messages from the application's message queue and send the messages to the WinProc for the main window. The loop will be exited when a WM_QUIT message is received, which is usually the result of the user closing the main window.
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
/* the return code from PostQuitMessage... */
}
Finally, a simple WinProc is added to the code (Figure 9). All this WinProc does is post a WM_QUIT message when it detects that the main window is being destroyed. This is done by calling the PostQuitMessage function. All other messages are passed on to the default window procedure. This code merely displays the main window on the screen and waits for the user to close the window.
Summary
So far I've introduced the message-passing paradigm that your applications must follow, and discussed window classes and window procedures. I began the application by showing the initialization code, which entailed registering the window classes, creating the initial window, and entering the standard Windows message loop. In the next installment, I will begin to fill in some of the information that goes into the resource file, such as the definition of the icon, and show you how to compile and link a Windows program. I will also discuss menus and set up the menu system for our application.