Programming ParadigmA Windows program, like any other interactive program, is for the most part input-driven. However, the input of a Windows program is conveniently pre-digested by the system. When the user hits the keyboard or moves the mouse, Windows intercepts such an event, pre-processes it and dispatches it to the appropriate user program. The program gets all its messages from Windows. It may do something about them, or not; in the latter case it lets Windows do the "right" thing (do the default processing). When a Windows program starts for the first time, it registers a Windows class (it’s not a C++ class) with the system. Through this class data structure it gives the system a pointer to a callback function called the Window Procedure. Windows will call this procedure whenever it wants to pass a message to the program, to notify it of interesting events. The name "callback" means just this: we don't call Windows, Windows will call us back. The program also gets a peek at every message in the message loop before it gets dispatched to the appropriate Windows Procedure. In most cases the message loop just forwards the message back to Windows to do the dispatching.
Figure. Input driven Windows paradigm Windows is a multi-tasking operating system. There may be many programs running at the same time. So how does Windows know which program should get a particular message? Mouse messages, for instance, are usually dispatched to the application that created the window over which the mouse cursor is positioned at a given moment (unless an application "captures" the mouse). Most Windows programs create one or more windows on the screen. At any given time, one of these windows has the focus and is considered active (its title bar is usually highlighted). Keyboard messages are sent to the window that has the focus. Events such as resizing, maximizing, minimizing, covering or uncovering a window are handled by Windows, although the concerned program that owns the window also gets a chance to process messages for these events. There are dozens and dozens of types of messages that can be sent to a Windows program. Each program handles the ones that it's interested in. Windows programs use Windows services to output text or graphics to the screen. Windows not only provides high level graphical interface, but it separates the program from the actual graphical hardware. In this sense Windows graphics is device independent. It is very easy for a Windows program to use standard Windows controls. Menus are easy to create, so are message boxes. Dialog boxes are more general--they can be designed by a programmer using a Dialog Editor, icons may be created using an Icon Editor. List boxes, edit controls, scroll bars, buttons, radio buttons, check boxes, etc., are all examples of built-in ready to use controls that make Windows programs so attractive and usable. All this functionality if available to the programmer through Windows API. It is a (very large) set of C functions, typedefs, structures and macros whose declarations are included in <windows.h> and whose code is linked to your program through a set of libraries and DLLs (dynamic-load libraries). Hello Windows!The simplest Windows program does nothing more but to create a window with the title "Hello Windows!" It is definitely more complicated than the Kernighan and Ritchie’s "Hello world!" and more complicated than our first "Hello!" C++ program. However, what we are getting here is much more than the simple old-fashioned teletype output. We are creating a window that can be moved around, resized, minimized, maximized, overlapped by other windows, etc. It also has a standard system menu in the upper left corner. So let’s not complain too much! In Windows, the main procedure is called WinMain. It must use the WINAPI calling convention and it is called by the system with the following parameters:
Notice the strange type names. You'll have to get used to them--Windows is full of typedefs. In fact, you will rarely see an int or a char in the description of Windows API. For now, it's enough to know that LPSTR is in fact a typedef for a char * (the abbreviation stands for Long Pointer to STRing, where string is a null terminated array and "long pointer" is a fossil left over from the times of 16-bit Windows). In what follows, I will consequently prefix all Windows API functions with a double colon. A double colon simply means that it's a globally defined function (not a member of any class or namespace). It is somehow redundant, but makes the code more readable.
First, we create a Window class and register it. Then we create a window with the caption "Hello Windows!" and display it (or not, depending on the flag we were given). Finally, we enter the message loop that waits for a message from Windows (the ::GetMessage API) and then lets the system dispatch it. The message will come back to us when Windows calls our Window procedure, WinProcedure. For the time being we don’t have to worry about the details of the message loop. The program normally terminates when ::GetMessage returns zero. The wParam of the last message contains the return code of our program.
The prototypes of all Windows APIs and data structures are in the main inlcude file <windows.h>. The class WinClassMaker encapsulates the WNDCLASS data structure as well as the ::RegisterClass API.
In the constructor of WinClassMaker we initialize all parameters to some sensible default values. For instance, we default the mouse cursor to an arrow, the brush (used by Windows to paint our window’s background) to the default window color. We have to provide the pointer to the Window procedure, the name of the class and the handle to the instance that owns the class.
Class WinMaker initializes and stores all the parameters describing a particular window.
The constructor of WinMaker takes the title of the window and the name of its Window class. The title will be displayed in the title bar, the class name is necessary for Windows to find the window procedure for this window. The rest of the parameters are given some reasonable default values. For instance, we let the system decide the initial position and size of our window. The style, WS_OVERLAPPEDWINDOW, is the most common style for top-level windows. It includes a title bar with a system menu on the left and the minimize, maximize and close buttons on the right. It also provides for a "thick" border that can be dragged with the mouse in order to resize the window.
All these paramters are passed to the ::CreateWindowEx API that creates the window (but doesn't display it yet).
Create returns a handle to the successfully created window. We will conveniently encapsulate this handle in a class called Window. Other than storing a handle to a particular window, this class will provide interface to a multitude of Windows APIs that operate on that window.
To make the window visible, we have to call ::ShowWindow with the appropriate parameter, which specifies whether the window should be initially minimized, maximized or regular-size. ::UpdateWindow causes the contents of the window to be refreshed. The window procedure must have the following signature, which is hard-coded into Windows:
Notice the calling convention and the types of parameters and the return value. These are all typedefs defined in windows.h. CALLBACK is a predefined language-independent calling convention (what order the parameters are pushed on the stack, etc.). LRESULT is a type of return value. HWND is a handle to a window, UINT is an unsigned integer that identifies the message, WPARAM and LPARAM are the types of the two parameters that are passed with every message. WinProcedure is called by Windows every time it wants to pass a message to our program. The window handle identifies the window that is supposed to respond to this message. Remember that he same Window procedure may service several instances of the same Windows class. Each instance will have its own window with a different handle, but they will all go through the same procedure.
Figure. The relationship between instances of the same program, their windows and the program’s Window class and procedure. The message is just a number. Symbolic names for these numbers are defined in windows.h. For instance, the message that tells the window to repaint itself is defined as
Every message is accompanied by two parameters whose meaning depends on the kind of message. (In the case of WM_PAINT the parameters are meaningless.) To learn more about window procedure study the help files that come with your compiler. Here’s our minimalist implementation of Window procedure.
It doesn’t do much in this particular program. It only handles one message, WM_DESTROY, that is sent to the window when it is being destroyed. At that point the window has already been closed--all we have to do is to terminate WinMain. We do it by posting the final quit message. We also pass the return code through it--zero in our case. This message will terminate the message loop and control will be returned to WinMain. EncapsulationA lot of Windows APIs take a large number of arguments. For instance, when you create a window, you have to be able to specify its position, size, style, title, and so on... That's why CreateWindowEx takes 12 arguments. RegisterClassEx also requires 12 parameters, but they are combined into a single structure, WNDCLASSEX. As you can see, there is no consistency in the design of Windows API. Our approach to encapsulating this type of APIs is to create a C++ class that combines all the arguments in one place. Since most of these arguments have sensible defaults, the constructor should initialize them appropriately. These parameters that don't have natural defaults are passed as arguments to the constructor. The idea being that, once an object of such a class is constructed, it should be usable without further modification. However, if modifications are desired, they should be done by calling appropriate methods. For instance, if we are to develop the class WinMaker into a more useful form, we should add methods such as SetPosition and SetSize that override the default settings for _x, _y, _width and _height. Let's analyze the two classes, WinClassMaker and WinMaker in this context. WinClassMaker encapsulates the API RegisterClassEx. The argument to this API is a structure which we can embed directly into our class. Three of the ten parameters--window procedure, class name and program instance--cannot be defaulted, so they are passed in the constructor. The window background color is normally defaulted to whatever the current system setting is--that's the COLOR_WINDOW constant. The mouse cursor in most cases defaults to whatever the system considers an arrow--that's the IDC_ARROW constant. The size of the structure must be set to sizeof (WNDCLASSEX). The rest of the parameters can be safely set to zero. We don't have to do anything else before calling WinClassMaker::Register. Of course, in a more sophisticated program, we might want to modify some of these settings, and we would most certainly add methods to do that. We'll talk about it later. In a similar way, the API CreateWindowEx is encapsulated in the class WinMaker. The non-defaultable parameters are the class name, the program instance and the title of the window. This time, however, we might want to call WinMaker::Create multiple times in order to create more than one window. Most likely these windows would have different titles, so we pass the title as an argumet to Create. To summarize, in the main procedure, your program creates a window of a particular class and enters the message processing loop. In this loop, the program waits idly for a message from Windows. Once the message arrives, it dispatches is back to Windows. The system then calls your Window procedure with the same message. You either process it yourself, or use the default processing. After returning from Window procedure, control goes back to the message loop and the whole process repeats itself. Eventually a "quit" message is posted and the loop ends. |