Reliable Software Logo
 Home  >  C++ Resources  > C++ In Action Book > Windows Techniques > Controlling Windows

C++ in Action: Windows Techniques

Controlling Windows through C++

Creating a Namespace

Before we start developing our Windows library any further, we should make an important naming decision. As you might have noticed, I employed a convention to use the prefix Win for all classes related to Windows. Such naming conventions used to make sense in the old days. Now we have better mechanisms, not only to introduce, but also to enforce naming conventions. I'm talking about namespaces.

It's easy to enclose all definitions of Windows-related classes, templates and functions in one namespace which we will conveniently call Win. The net result, for the user of our library, will be to have to type Win::Maker instead of WinMaker, Win::Dow instead of Window, etc. It will also free the prefix Win for the use in user-defined names (e.g., the client of our library will still be able to define his or her own class called WinMaker, without the fear of colliding with library names).

Also, in preparation for further development, let's split our code into multiple separate files. The two classes Win::ClassMaker and Win::Maker will each get a pair of header/implementation files. The Win::Procedure function will also get its own pair, since the plan is to make it part of our library.

Model-View-Controller

The biggest challenge in Windows programming is to hide the ugliness of the big switch statement that forms the core of a window procedure. We'll approach this problem gradually.

The first step is to abstract the user interface of the program from the engine that does the actual work. Traditionally, the UI-independent part is called the model. The UI part is split into two main components, the view and the controller.

The view's responsibility is to display the data to the user. This is the part of the program that draws pictures or displays text in the program's window(s). It obtains the data to be displayed by querying the model.

The controller's responsibility is to accept and interpret user input. When the user types in text, clicks the mouse button or selects a menu item, the controller is the first to be informed about it. It converts the raw input into something intelligible to the model. After notifying the model it also calls the view to update the display (in a more sophisticated implementation, the model might selectively notify the view about changes).

The model-view-controller paradigm is very powerful and we'll use it in our encapsulation of Windows. The problem is, how do we notify the appropriate controller when a window procedure is notified of user input? The first temptation is to make the Controller object global, so that a window procedure, which is a global function, can access it. Remember however that there may be several windows and several window procedures in one program. Some window procedures may be shared by multiple windows. What we need is a mapping between window handles and controllers. Each window message comes with a window handle, HWND, which uniquely identifies the window for which it was destined.

The window-to-controller mapping can be done in many ways. For instance, one can have a global map object that does the translation. There is, however, a much simpler way--we can let Windows store the pointer to a controller in its internal data structures. Windows keeps a separate data structure for each window. Whenever we create a new window, we can create a new Controller object and let Windows store the pointer to it. Every time our window procedure gets a message, we can retrieve this pointer using the window handle passed along with the message.

The APIs to set and retrieve an item stored in the internal Windows data structure are SetWindowLong and GetWindowLong. You have to specify the window whose internals you want to access, by passing a window handle. You also have to specify which long you want to access--there are several pre-defined longs, as well as some that you can add to a window when you create it. To store the pointer to a controller, we'll use the long called GWL_USERDATA. Every window has this long, even a button or a scroll bar (which, by the way, are also windows). Moreover, as the name suggests, it can be used by the user for whatever purposes.

We'll be taking advantage of the fact that a pointer has the same size as a long--will this be true in 64-bit Windows, I don't know, but I strongly suspect.

There is a minor problem with the Get/SetWindowLong API: it is typeless. It accepts or returns a long, which is not exactly what we want. We'd like to make it type-safe. To this end, let's encapsulate both functions in templates, parametrized by the type of the stored data.

namespace Win
{
    template <class T>
    inline T GetLong (HWND hwnd, int which = GWL_USERDATA)
    {
        return reinterpret_cast<T> (::GetWindowLong (hwnd, which));
    }

    template <class T>
    inline void SetLong (HWND hwnd, T value, int which = GWL_USERDATA)
    {
        ::SetWindowLong (hwnd, which, reinterpret_cast<long> (value));
    }
}

In fact, if your compiler supports member templates, you can make GetLong and SetLong methods of Win::Dow.

namespace Win
{
    class Dow
    {
    public:
        Dow (HWND h = 0) : _h (h) {}
        template <class T>
        inline T GetLong (int which = GWL_USERDATA)
        {
            return reinterpret_cast<T> (::GetWindowLong (_h, which));
        }
        template <class T>
        inline void SetLong (T value, int which = GWL_USERDATA)
        {
            ::SetWindowLong (_h, which, reinterpret_cast<long> (value));
        }

        void Display (int cmdShow)
        {
            assert (_h != 0);
            ::ShowWindow (_h, cmdShow); 
            ::UpdateWindow (_h); 
        }
    private:
        HWND _h;
    };
}

Notice the use of default value for the which argument. If the caller calls any of these functions without the last argument, it will be defaulted to GWL_USERDATA.

Are default arguments already explained???

We are now ready to create a stub implementation of the window procedure.

LRESULT CALLBACK Win::Procedure (HWND hwnd, 
                                 UINT message, 
                                 WPARAM wParam, 
                                 LPARAM lParam)
{
    Controller * pCtrl = Win::GetLong<Controller *> (hwnd);
    switch (message)
    {
    case WM_NCCREATE:
        {
            CreateData const * create = 
                reinterpret_cast<CreateData const *> (lParam);
            pCtrl = static_cast<Controller *> (create->GetCreationData ());
            pCtrl->SetWindowHandle (hwnd);
            Win::SetLong<Controller *> (hwnd, pCtrl);
        }
        break;
    case WM_DESTROY:
        // We're no longer on screen
        pCtrl->OnDestroy ();
        return 0;
    case WM_MOUSEMOVE:
        {
            POINTS p = MAKEPOINTS (lParam);
            KeyState kState (wParam);
            if (pCtrl->OnMouseMove (p.x, p.y, kState))
                return 0;
        }
    }
    return ::DefWindowProc (hwnd, message, wParam, lParam);
}
						

We initialize the GWL_USERDATA slot corresponding to hwnd in one of the first messages sent to our window. The message is WM_NCCREATE (Non-Client Create), sent before the creation of the non-client part of the window (the border, the title bar, the system menu, etc.). (There is another message before that one, WM_GETMINMAXINFO, which might require special handling.) We pass the pointer to the controller as window creation data. We use the class Win::CreateData, a thin encapsulation of Windows structure CREATESTRUCT. Since we want to be able to cast a pointer to CREATESTRUCT passed to us by Windows to a pointer to Win::CreateData, we use inheritance rather than embedding (you can inherit from a struct, not only from a class).

namespace Win
{
    class CreateData: public CREATESTRUCT
    {
    public:
        void * GetCreationData () const { return lpCreateParams; }
        int GetHeight () const { return cy; }
        int GetWidth () const { return cx; }
        int GetX () const { return x; }
        int GetY () const { return y; }
        char const * GetWndName () const { return lpszName; }
    };
}

The message WM_DESTROY is important for the top-level window. That's where the "quit" message is usually posted. There are other messages that might be sent to a window after WM_DESTROY, most notably WM_NCDESTROY, but we'll ignore them for now.

I also added the processing of WM_MOUSEMOVE, just to illustrate the idea of message handlers. This message is sent to a window whenever a mouse moves over it. In the generic window procedure we will always unpack message parameters and pass them to the appropriate handler.

There are three parameters associated with WM_MOUSEMOVE, the x coordinate, the y coordinate and the state of control keys and buttons. Two of these parameters, x and y, are packed into one LPARAM and Windows conveniently provides a macro to unpack them, MAKEPOINTS, which turns lParam into a structure called POINTS. We retrieve the values of x and y from POINTS and pass them to the handler.

The state of control keys and buttons is passed inside WPARAM as a set of bits. Access to these bits is given through special bitmasks, like MK_CONTROL, MK_SHIFT, etc., provided by Windows. We will encapsulate these bitwise operations inside a class, Win::KeyState.

class KeyState
{
public:
    KeyState (WPARAM wParam): _data (wParam)
    {}
    bool IsCtrl () const { return (_data & MK_CONTROL) != 0; }
    bool IsShift () const { return (_data & MK_SHIFT) != 0; }
    bool IsLButton () const { return (_data & MK_LBUTTON) != 0; }
    bool IsMButton () const { return (_data & MK_MBUTTON) != 0; }
    bool IsRButton () const { return (_data & MK_RBUTTON) != 0; }
private:
    WPARAM    _data;
};

The methods of Win::KeyState return the state of the control and shift keys and the state of the left, middle and right mouse buttons. For instance, if you move the mouse while you press the left button and the shift key, both IsShift and IsLButton will return true.

In WinMain, where the window is created, we initialize our controller and pass it to Win::Maker::Create along with the window's title.

        TopController ctrl;
        win.Create (ctrl, "Simpleton");
						

This is the modified Create. It passes the pointer to Controller as the user-defined part of window creation data--the last argument to CreateWindowEx.

HWND Maker::Create (Controller & controller, char const * title)
{
    HWND hwnd = ::CreateWindowEx (
        _exStyle,
        _className,
        title,
        _style,
        _x,
        _y,
        _width,
        _height,
        _hWndParent,
        _hMenu,
        _hInst,
        &controller);

    if (hwnd == 0)
        throw "Internal error: Window Creation Failed.";
    return hwnd;
}

To summarize, the controller is created by the client and passed to the Create method of Win::Maker. There, it is added to the creation data, and Windows passes it as a parameter to WM_NCREATE message. The window procedure unpacks it and stores it under GWL_USERDATA in the window's internal data structure. During the processing of each subsequent message, the window procedure retrieves the controller from this data structure and calls its appropriate method to handle the message. Finally, in response to WM_DESTROY, the window procedure calls the controller one last time and unplugs it from the window.

Now that the mechanics of passing the controller around are figured out, let's talk about the implementation of Controller. Our goal is to concentrate the logic of a window in this one class. We want to have a generic window procedure that takes care of the ugly stuff--the big switch statement, the unpacking and re-packing of message parameters and the forwarding of the messages to the default window procedure. Once the message is routed through the switch statement, the appropriate Controller method is called with the correct (strongly-typed) arguments.

For now, we'll just create a stub of a controller. Eventually we'll be adding a lot of methods to it--as many as there are different Windows messages.

The controller stores the handle to the window it services. This handle is initialized inside the window procedure during the processing of WM_NCCREATE. That's why we made Win::Procedure a friend of Win::Controller. The handle itself is protected, not private--derived classes will need access to it. There are only two message-handler methods at this point, OnDestroy and OnMouseMove.

namespace Win
{
    class Controller
    {
        friend LRESULT CALLBACK Procedure (HWND hwnd, 
                        UINT message, WPARAM wParam, LPARAM lParam);

        void SetWindowHandle (HWND hwnd) { _h = hwnd; }
    public:
        virtual ~Controller () {}
        virtual bool OnDestroy () 
            { return false; }
        virtual bool OnMouseMove (int x, int y, KeyState kState) 
            { return false; }
    protected:
        HWND  _h;
    };
}

You should keep in mind that Win::Controller will be a part of the library to be used as a base class for all user-defined controllers. That's why all message handlers are declared virtual and, by default, they return false. The meaning of this Boolean is, "I handled the message, so there is no need to call DefWindowProc." Since our default implementation doesn't handle any messages, it always returns false.

The user is supposed to define his or her own controller that inherits from Win::Controller and overrides some of the message handlers. In this case, the only message handler that has to be overridden is OnDestroy--it must close the application by sending the "quit" message. It returns true, so that the default window procedure is not called afterwards.

class TopController: public Win::Controller
{
public:
    bool OnDestroy ()
    {
        ::PostQuitMessage (0);
        return true;
    }
};

To summarize, our library is designed in such a way that its client has to do minimal work and is protected from making trivial mistakes. For each class of windows, the client has to create a customized controller class that inherits from our library class, Win::Controller. He implements (overrides) only those methods that require non-default implementation. Since he has the prototypes of all these methods, there is no danger of misinterpreting message parameters. This part--the interpretation and unpacking--is done in our Win::Procedure. It is written once and for all, and is thoroughly tested.

This is the part of the program that is written by the client of our library. In fact, we will simplify it even more later.

Is it explained that the result of assignment can be used in an expression?

#include "Class.h"
#include "Maker.h"
#include "Procedure.h"
#include "Controller.h"

class TopController: public Win::Controller
{
public:
    bool OnDestroy ()
    {
        ::PostQuitMessage (0);
        return true;
    }
};

int WINAPI WinMain
   (HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdParam, int cmdShow)
{
    char className [] = "Simpleton";
    Win::ClassMaker winClass (className, hInst);
    winClass.Register ();
    Win::Maker maker (className, hInst);
    TopController ctrl;
    Win::Dow win = maker.Create (ctrl, "Simpleton");
    win.Display (cmdShow);

    MSG  msg;
    int status;
    while ((status = ::GetMessage (& msg, 0, 0, 0)) != 0)
    {
        if (status == -1)
            return -1;
        ::DispatchMessage (& msg);
    }
    return msg.wParam;
}

Notice that we no longer have to pass window procedure to class maker. Class maker can use our generic Win::Procedure implemented in terms of the interface provided by our generic Win::Controller. What will really distinguish the behavior of one window from that of another is the implementation of a controller passed to Win::Maker::Create.

The cost of this simplicity is mostly in code size and in some minimal speed deterioration.

Let's start with speed. Each message now has to go through parameter unpacking and a virtual method call--even if it's not processed by the application. Is this a big deal? I don't think so. An average window doesn't get many messages per second. In fact, some messages are queued in such a way that if the window doesn't process them, they are overwritten by new messages. This is for instance the case with mouse-move messages. No matter how fast you move the mouse over the window, your window procedure will not choke on these messages. And if a few of them are dropped, it shouldn't matter, as long as the last one ends up in the queue. Anyway, the frequency with which a mouse sends messages when it slides across the pad is quite arbitrary. With the current processor speeds, the processing of window messages takes a marginally small amount of time.

Program size could be a consideration, except that modern computers have so much memory that a megabyte here and there doesn't really matter. A full blown Win::Controller will have as many virtual methods as there are window messages. How many is it? About 200. The full vtable will be 800 bytes. That's less than a kilobyte! For comparison, a single icon is 2kB. You can have a dozen of controllers in your program and the total size of their vtables won't even reach 10kB.

There is also the code for the default implementation of each method of Win::Controller. Its size depends on how aggressively your compiler optimizes it, but it adds up to at most a few kB.

Now, the worst case, a program with a dozen types of windows, is usually already pretty complex--read, large!--plus it will probably include many icons and bitmaps. Seen from this perspective, the price we have to pay for simplicity and convenience is minimal.

Exception Specification

What would happen if a Controller method threw an exception? It would pass right through our Win::Procedure, then through several layers of Windows code to finally emerge through the message loop. We could, in principle catch it in WinMain. At that point, however, the best we could do is to display a polite error message and quit. Not only that, it's not entirely clear how Windows would react to an exception rushing through its code. It might, for instance, fail to deallocate some resources or even get into some unstable state. The bottom line is that Windows doesn't expect an exception to be thrown from a window procedure.

We have two choices, either we put a try/catch block around the switch statement in Win::Procedure or we promise not to throw any exceptions from Controller's methods. A try/catch block would add time to the processing of every single message, whether it's overridden by the client or not. Besides, we would again face the problem, what to do with such an exception. Terminate the program? That seems pretty harsh! On the other hand, the contract not to throw exceptions is impossible to enforce. Or is it?!

Enter exception specifications. It is possible to declare what kind of exceptions can be thrown by a function or method. In particular, we can specify that no exceptions can be thrown by a certain method. The declaration:

virtual bool OnDestroy () throw ();

promises that OnDestroy (and all its overrides in derived classes) will not throw any exceptions. The general syntax is to list the types of exceptions that can be thrown by a procedure, like this:

void Foo () throw (bad_alloc, char *);

How strong is this contract? Unfortunately, the standard doesn't promise much. The compiler is only obliged to detect exception specification mismatches between base class methods and derived class overrides. In particular, the specification can be only made stronger (fewer exceptions allowed). There is no stipulation that the compiler should detect even the most blatant violations of this promise, for instance an explicit throw inside a method defined as throw() (throw nothing). The hope, however, is that compiler writers will give in to the demands of programmers and at least make the compiler issue a warning when an exception specification is violated. Just as it is possible for the compiler to report violations of const-ness, so it should be possible to track down violations of exception specifications.

For the time being, all that an exception specification accomplishes in a standard-compliant compiler is to guarantee that all unspecified exceptions will get converted to a call to the library function unexpected (), which by default terminates the program. That's good enough, for now. Declaring all methods of Win::Controller as "throw nothing" will at least force the client who overrides them to think twice before allowing any exception to be thrown.

Cleanup

It's time to separate library files from application files. For the time being, we'll create a subdirectory "lib" and copy all the library files into it. However, when the compiler compiles files in the main directory, it doesn't know where to find library includes, unless we tell it. All compilers accept additional include paths. We'll just have to add "lib" to the list of additional include paths. As part of the cleanup, we'll also move the definition of TopController to a separate file, control.h.


Next PageNext: Painting