The WinMain Procedure

Just as every DOS C program has a procedure titled main at its heart, every Windows program has a similar entry point with the title WinMain (and, yes, this title is case-sensitive). Also, just as a DOS C program may include provisions within the main procedure declaration to retrieve command-line parameters, the WinMain declaration includes a similar provision in the lpszCmdParam parameter, even though command-line parameters are rarely used under Windows.

TIP

When applications are created using the MFC foundation classes and the AppWizard, the WinMain procedure will not appear in the skeleton source files created for the various classes. However, the WinMain procedure has not vanished, it is now buried—out of sight and out of mind— inside the precompiled foundation class library. Although invisible, WinMain is still present and, if absolutely necessary, can be passed instructions to change the initial style and characteristics of the application.

In contrast to DOS programming, the declarations used for WinMain are not optional and must be declared in the exact order and form shown, regardless of whether each specific argument will be used or ignored. Remember, because the application’s WinMain procedure is being called only indirectly by the user, with the actual calling format supplied by Windows, the calling format flexibility present in a DOS context is absent under Windows.

Also note that the reserved word PASCAL is used in all exported function declarations, indicating to the compiler that Pascal rather than C ordering is used for all arguments (values) pushed onto the stack. While C commonly uses inverted order, placing the least-significant bytes first on the stack, Windows uses Pascal ordering that, like Unix, places the most-significant bytes first.

There is another small but crucial difference. Exported functions are functions that will be called from outside the class or—in the case of a DLL— from other applications outside the unit (library). In a Windows application where subroutines are called from Windows itself or where a member function in one class is called from outside the class, even if both belong to the same application, the Pascal calling order is necessary.

On the other hand, all internal function declarations (functions and subprocedures called directly from other procedures within the application) will expect arguments to appear in standard C order and should not be declared using the PASCAL specification.

As far as how argument lists are declared in the procedure definitions, it makes absolutely no difference whether the Pascal or C calling conventions are used. These conventions affect only how the arguments are handled internally—that is, on the stack—and do not in any way affect how the programmer constructs the argument lists.

Of the four calling arguments shown below, the first two are of primary importance. The data type HANDLE refers to a 32-bit unsigned value; the hInstance and hPrevInstance arguments are unique identifiers supplied by the Windows 98 system. Unlike DOS where only one program (TSRs excepted) is active at a time, multitasking systems require unique identification, not only for each application, but also for each instance of an application that may be executing. Therefore, the hInstance and hPrevInstance parameters are assigned only when an application instance becomes active. They provide the equivalents of the task ID and process ID values common in other multitasking environments.

int PASCAL WinMain( HANDLE hInstance, 
                    HANDLE hPrevInstance,
                    LPSTR  lpszCmdParam, 
                    int    nCmdShow )

NOTE

The data types used in these declarations may be unfamiliar to DOS programmers. See the introduction to Windows data types later in this chapter for more details. Windows programmers should note that the 16-bit HANDLE used in Windows 3.x is now a 32-bit unsigned value, which is a change that affects a number of aspects of Windows 98 programming.

The hPrevInstance (previous instance) identifier is the hInstance identifier previously assigned to the most recent instance of an application that is already executing. If there is no previous instance of the application currently running, which is frequently the case, this argument will be NULL (0). The reasons for this second process identifier will be demonstrated presently.

The third parameter, lpszCmdParam, is a long (FAR) pointer to a null-terminated (ASCIIZ) string containing any command-line parameters passed to the program instance. Although the command-line parameters are provided by Windows rather than by a conventional (DOS) command line, the user can specify these parameters through the Run dialog box invoked from the Start menu. In general, however, Windows applications rely on dialog boxes for specific input and on .INI entries for default values, rather than expecting command-line parameters.

The fourth calling parameter, nCmdShow, is simply an integer argument indicating whether the newly launched application will be displayed as a normal window, or initially displayed as an icon. Next, following the procedure declaration itself, a brief list of local variable declarations appears:

{
   static char szAppName[] = “WinHello”;
   HWND        hwnd;
   MSG         msg;
   WNDCLASS    wc;

The HWND data type identifies a window handle; MSG identifies a message value; and WNDCLASS refers to a record structure used to pass a number of values relevant to the application’s main window. We’ll discuss these data types in more detail later in the chapter.

Registering a Window Class

The first task accomplished within the WinMain procedure depends on the hPrevInstance argument passed. If a previous instance of this application is already active, there is no need to register the window class a second time. It’s more likely, of course, that this is the first instance of the application (hPrevInstance is NULL) and, therefore, the window class definitions must be assigned and the window class registered.

The wc structure is defined in Windows.H (which must be included in all Windows applications). Of the WNDCLASS record fields, the second (lpfnWndProc) and last (lpszClassName) are the most important. The remainder of the fields can usually remain unchanged from one application to another. (See the section about Template.C at the end of this chapter for another example.)

The first field is the window-style specification. In this example, it is assigned two style flag values (combined by ORing bitwise). The CS_ flags are defined in Windows.H as 16-bit constants, each with one flag bit set. Here, the CS_HREDRAW and CS_VREDRAW flags indicate that the window should be redrawn completely anytime the horizontal or vertical size changes. Thus, for the WinHello demo, if the window size changes, the window display is completely redrawn, with the “Hello, World” message string recentered in the new display.

   if( ! hPrevInstance )
   {
      wc.style         = CS_HREDRAW | CS_VREDRAW;
      wc.lpfnWndProc   = WndProc;

The second field in the WNDCLASS structure, lpfnWndProc, is a pointer to the exported procedure—WndProc in this example—that will handle all windows messages for this application. The type prefix lpfn identifies this field as a “long pointer to function.” But you should realize that these prefix conventions are provided for the benefit of the programmer. They are not absolutes, nor do these designations place any constraints on the compiler. However, predefined fields and identifiers can be considered an exception. Although you can change these, they are best left as defined, if only for the simple reason that redefinitions could easily result in a cascade of changes and confusion.

The next two record fields are integers, which are reserved to specify extra information about the class or window styles. Commonly, neither is required and, by default, both are initialized as zeros (0). (Incidentally, the cb_ prefix stands for “count of bytes.”)

      wc.cbClsExtra    = 0;
      wc.cbWndExtra    = 0;
      wc.hInstance     = hInstance;

The next field, hInstance, is simply the recipient of the hInstance argument passed by Windows when the program is initially called. This field assignment is constant for all applications.

The next three data fields currently assign default values for the application’s icon, cursor, and background color and pattern.

      wc.hIcon         = LoadIcon( NULL, IDI_APPLICATION );
      wc.hCursor       = LoadCursor( NULL, IDC_ARROW );
      wc.hbrBackground = GetStockObject( WHITE_BRUSH );

The default IDI_APPLICATION specification for the icon assigns the predefined image of a white square with a black border. The  IDC_ARROW cursor assigns the stock cursor graphic of a slanted arrow. In the third assignment, the hbrBackground field contains the background color and pattern used for the application’s client region. (The hbr stands for “handle to brush,” where brush refers to a pixel pattern used to fill or paint an area.)

Next, because this application does not have a menu assigned, the menu name is entered as a null value. The class name (lpszClassName) is assigned the null-terminated (ASCIIZ) string defined previously.

      wc.lpszMenuName  = NULL;
      wc.lpszClassName = szAppName;
      RegisterClass( &wc );
   }

And, last within this conditional subprocess, the RegisterClass function is called with the wc structure passed as a parameter (by address) to register this window class definition with the Windows 98 operating system. As mentioned previously, this registration is required only once. Thereafter, the registration and values assigned are available to any new instances of the application, as long as any instance of the application remains active. Once all instances of the application have been closed, the window class registration is discarded, and any future instance will need to execute the class registration process again.

Creating an Application Window

While the previous step, registering a window class, defined characteristics that are common to all instances of the application, the application window itself still must be created. Unlike the RegisterClass function call, which is called only once, the CreateWindow function must be called by every instance of the application to produce the actual window display.

The handle to the application window that is returned by the CreateWindow function will be used later as an argument in other function calls; it will be used as a unique identifier for the actual window belonging to the application instance. While many properties of the application class have already been defined, other properties specific to this instance of the application have not; they are passed now as parameters to the CreateWindow function.

   hwnd = CreateWindow(                                  
      szAppName,                // window class name       
      “Hello, World – Windows_98 Style”,
                                // window caption          

The first two parameters passed are the application class name—the same ASCIIZ string that was used when the class was registered—and the application’s initial window caption. Of course, the second of these is optional, and, if the window is defined without a caption bar or if no caption is desired, this parameter should be passed as NULL.

The third parameter defines the window style and, generically, is passed as WS_OVERLAPPEDWINDOW, a value that is a combination of individual flags defined in Windows.H.

      WS_OVERLAPPEDWINDOW,      // window style            
      CW_USEDEFAULT,            // initial X position      
      CW_USEDEFAULT,            // initial Y position      
      CW_USEDEFAULT,            // initial X size          
      CW_USEDEFAULT,            // initial Y size          

The fourth through seventh parameters establish the application window’s initial position and size. They can be passed as explicit values or, more often, as CW_USEDEFAULT. This parameter instructs Windows to use the default values for an overlapped window, positioning each successive overlapped window at a stepped horizontal and vertical offset from the upper-left corner of the screen.

The next parameter, the eighth, is passed as NULL for the simple reason that this application is not associated with a parent window. Alternatively, if this window were to be called as a child process belonging to another application, the parent’s window handle would be passed as a parameter here.

      NULL,                     // parent window handle    
      NULL,                     // window menu handle      

The ninth parameter used in calling the CreateWindow function is also passed as NULL, directing the application to use the default system menu. Note, however, that the menu in question is the window frame’s pull-down menu (upper-left icon on most window frames), not the menu bar (or toolbar), which is defined as an application resource and assigned during the application class registration.

The tenth calling parameter, which can never be passed as NULL, is the same instance handle originally supplied by the system.

      hInstance,                // program instance handle
      NULL  );                  // creation parameters     

The final parameter, again NULL in this example, may in other cases provide a pointer to additional data for use either by the application window or by some subsequent process. In most examples, however, this will be an empty (NULL) argument.

Showing the Window

Now, after CreateWindows has been called, the application window has been created internally in Windows 98’s “world view” but does not yet appear on the actual screen display. Therefore, the next step is to call the ShowWindow function, passing as parameters the hwnd value returned by CreateWindow and the nCmdShow argument supplied when WinMain was initially called.

   ShowWindow( hwnd, nCmdShow );
   UpdateWindow( hwnd );

The ShowWindow function, contrary to what you might assume, does only a portion of the task of creating (painting) the window display. It is principally responsible for creating the window frame, caption bar, menu bar, and minimize/maximize buttons. But this function does not create the client window area—the display area specific to the application itself. Therefore, one more function call is necessary before the window display is complete: a call to the UpdateWindow function with the hwnd window handle as an argument (this call actually posts a WM_PAINT message to the application instructing it to repaint its own window area—a process that will be discussed in a moment).

This completes the process of registering a window class, defining and creating the window itself, and updating the screen to show the window. But while more than a small task, this is only preparation for the application; the real task has not yet begun ... but will momentarily. One last, but very essential, portion of the WinMain function remains: the message-handling loop.

Handling Messages

Windows creates and manages a separate message queue for each active Windows program instance. Thus, when any keyboard or mouse event occurs, Windows translates this event into a message value. This value is placed in the application’s message queue, where it waits until it is retrieved by the application instance, which is precisely the purpose of the message-handling loop.

TIP

An alternative to the message-handling loop, the use of CALLBACK procedures, is discussed and illustrated in Chapter 3.

The message-handling loop begins by calling the GetMessage function to retrieve messages from the application instance’s message queue. As long as the message retrieved is not a WM_QUIT message (0x0012), GetMessage will return a TRUE (non-zero) result. The actual message value is returned in the msg structure, which was passed by address.

   while( GetMessage( &msg, NULL, 0, 0 ) )
   {

The syntax for the GetMessage function is defined as:

BOOL GetMessage( lpMsg, HWND, wMsgFilterMin, wMsgFilterMax )

In most cases, only the first parameter is actually used to return the message itself. The remaining three parameters are usually passed as NULL or zero.

The initial parameter is a pointer to a message structure to receive the message information retrieved and, subsequently, to pass this data on through to the TranslateMessage and DispatchMessage functions. Without this parameter, there would obviously be little point in calling the GetMessage function at all.

The second parameter is optional but can be used to identify a specific window (belonging to the calling application) and to restrict retrieval to messages that belong to that window. When passed as NULL, as in the present example, GetMessage retrieves all messages addressed to any window belonging to the application placing the call. The GetMessage function does not retrieve messages addressed to windows belonging to any other application.

The third and fourth parameters provide filter capabilities, restricting the message types returned. When both parameters are passed as 0, no filtering occurs. Alternatively, constants such as WM_KEYFIRST and WM_KEYLAST could be passed as filter values to restrict message retrieval to keyboard events or, by using WM_MOUSEFIRST and WM_MOUSELAST, to retrieve only mouse-related messages.

Filters and window selection aside, the GetMessage function (together with the PeekMessage and WaitMessage functions) has another important characteristic.

Conventionally, loop statements monopolize the system until terminated, thus preempting or preventing other operations for the duration of the loop. And, in other circumstances—remember this as a caution—even under Windows, loop operations can tie up system resources.

The GetMessage function, however, has the ability to pre-empt the loop operation to yield control to other applications when no messages are available for the current application, or when WM_PAINT or WM_TIMER messages directed to other tasks are available. Thus, it can give other applications their share of CPU time to execute.

For the present, when the application receives an event message (other than WM_QUIT), the message value is passed. First, it goes to the Windows TranslateMessage function for any keystroke translation that may be specific to the application. Then it is passed to the DispatchMessage handler, where the message information is passed to the next appropriate message-handling procedure (back to Windows, either for immediate handling or, indirectly, for forwarding to the exported WndProc procedure).

      TranslateMessage( &msg );
      DispatchMessage( &msg );
   }

Finally, when the message-processing loop terminates, the wParam argument from the final message retrieved is, in turn, returned to the calling application—the system Desktop itself.

   return msg.wParam;
}

© 1998 SYBEX Inc. All rights reserved.