Journal Articles

CVu Journal Vol 8, #2 - Apr 1996 + Programming Topics
Browse in : All > Journals > CVu > 082 (9)
All > Topics > Programming (877)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: Understanding Windows (9)

Author: Administrator

Date: 03 April 1996 13:15:27 +01:00 or Wed, 03 April 1996 13:15:27 +01:00

Summary: 

Body: 

Imagine that you want to write a simple text editor. This happens to be extremely simple to achieve by creating an 'SDI' (Single Document Interface) application, i.e. a frame window with menu bar. You then place an edit control on the client area of that window. In this case the edit control with appropriate style settings will do nearly all of the work for you. In order to complete a minimal program you simply need to handle the file loading and saving. Communicating with the edit field is done largely through SendMessage(). In order to edit multiple documents the user need only start multiple instances of the application. Obviously I have just described the bones of Microsoft Notepad (which doesn't add much flesh to the above).

The most obvious problem with this scheme is that with each instance of your editor leading an independent life, working on a number of files at a time is too cumbersome for most purposes. The approved solution is the 'MDI'(Multiple Document Interface) which is directly supported by the API.

MDI Applications

Windows provides a system global window class "MDICLIENT". A window of this class is placed on an application defined 'frame' window. The MDI client window manages an indefinite number of application defined 'MDI Child windows' within its borders. The child window instances may be of any application registered class but need to be created by the MDI client window.

The Frame Window

This will be very like an SDI frame window with any combination of menu, toolbar, status bar and any other user interface components. It needs to create and determine the size and position of just one MDI client window. Default message handling is handled by the API function DefFrameProc() instead of DefWindowProc().

The MDI Client Window

Being a child of the frame window and parent to the MDI child windows, this window is in an excellent position to manage aspects of both. The Frame's menu should include a 'Window' popup that the client looks after. It appends child window titles to this popup menu counting from '1' to '9' and then "More windows...".

Creation of child windows is performed in response to WM_MDICREATE messages. Other WM_MDI* messages control the child windows and return information.

The MDI Children

If you are writing a multiple file editor then you may only need one class of child window, if you are writing an I.D.E. then you will probably want several classes.

These are child windows and don't have menus of their own. Because the menu and any other source of commands is on the frame window, the task of handling the commands and updating menu and toolbar states has to be shared between the frame and the currently active child window. Default message processing is handled by the API function DefMdiChildProc().

Writing your own

Sadly there is just too much code needed to actually cover the whole process here but there are plenty of examples to be found within the SDK (specifically 'multipad' a multiple file version of notepad) and in several books including Petzold's.

Assuming that we can assemble the source code to implement an MDI application, there remains one problem that I haven't so far touched upon...

Associating with window instances

The window procedure associated with each MDI child window is also associated with any other window instance of the same class. Remember, the window procedure is set for a registered window class, that is one of these:

LRESULT __export CALLBACK OurWndProc(HWND hWnd, UINT Msg,
    WPARAM, LPARAM );

The only thing that gives us a clue as to which instance of the window class this call arrived for is the hWnd (handle to window) parameter. Clearly this handle will be associated with some data object - such as a text file name in the case of an editor. A little thought leads to the conclusion that the most generally useful association will be with a pointer to a data structure within our application.

There are in fact three possible ways of making this association.

Dictionary based association

When we created the window originally, we could have saved the window handle and our data pointer in a dictionary of some sort. Subsequently in our window procedure we can look up the hWnd value in the dictionary and retrieve the associated pointer for use within the procedure. This is essentially what is done by MFC and other large Windows class libraries.

Another approach is to make the association within the system.

Window properties

Windows has an internal mechanism to associate data identified by string field identifiers with window handles. The SetProp(), GetProp() and RemoveProp() API functions are available (see SDK documentation). These are relatively inefficient and should not be used for this purpose as they have no obvious advantages over the application dictionary approach except as a quick and dirty solution.

Padding the window instance

The alternative is to use extra bytes in the window instance itself. When registering a window class, one member of the WNDCLASS structure is 'cbWndExtra' (count of bytes window extra). If this is set to the size of a pointer to your data structure, then each window instance of this class that is created will have space for an extra pointer tacked on the end. Although the window handle is an offset in a segment owned by the system and therefore not directly accessible by the application, it can be accessed by a set of API functions. These functions are SetWindowWord(), SetWindowLong(), GetWindowWord() and GetWindowLong().

All of these functions take a window handle and an offset. The offset is from the end of the base window structure. There are a number of constant negative offsets that you can use to obtain documented members of the window instance. An offset of zero will give access to the start of the extra bytes, in this case our pointer. It is a matter of choice whether to optimise the number of the extra bytes and select the correct pair of access functions or just use four bytes and use the long version. Obviously the less bulky you make each window instance, the less strain on USER resources they will cause.

The C++ approach

Anyone who knows me will have realised by now that this has been leading up to a C++ approach. What we want to do is to derive our classes from base classes provided by the Windows system, specialising them for our own purposes. Of course if the Windows API was C++ then this would be easy. It's easy to envisage having a base window class from which we can derive all our specialised ones. Sadly it won't turn out to be that easy!

Going at least part of the hog

If we can rely on every window having a pointer at an offset of zero as accessed by the Get/SetWindowWord/ Long() functions then it is easy to see that we could have just one window procedure for all our windows. Suppose that we have a base class like this:

class WinBase { HWND hWnd;
    static LRESULT __export CALLBACK AllWndProc( HWND, UINT, WPARAM, LPARAM );
    virtual LRESULT WndProc( UINT, WPARAM, LPARAM ); public: WinBase(); BOOL
    Create( /* params for window */ ); }; LRESULT __export CALLBACK
    WinBase::AllWndProc( HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM
    lParam ){ WinBase *pWB = GetWindowWord( hWnd, 0 ); return pWB->WndProc(
    uMessage, wParam, lParam ); } BOOL WinBase::Create( /* params for window
    */ ){ /* register the class with extra window bytes if not yet registered
    and set the callback to AllWndProc, Create the window, put the 'this'
    pointer into the window instance using SetWindowWord(hWnd, 0, this);
    assign hWnd */ }

This would nearly work! Unfortunately there are a number of messages sent directly to the window procedure before CreateWindow() returns, so we need to set the 'this' pointer earlier.

This can be achieved by adding a static WinBase pointer and picking this up in AllWndProc() on receipt of the first message and setting the window instance pointer at that time. This can be made to work but it needs the window procedure to check the return value each time it retrieves it or to check another static flag set just before the CreateWindow() call. Neither option is totally satisfactory so here is an alternative.

Keep the static WinBase pointer:

static
    WinBase *pWBCreating;

In the WinBase::Create() function, prior to calling CreateWindow() add:

pWBCreating
    = this;

Also instead of supplying AllWndProc as the window class procedure, set it to FirstWndProc.

Define FirstWndProc thus:

LRESULT
    __export CALLBACK WinBase::FirstWndProc( HWND hWnd, UINT uMessage, WPARAM
    wParam, LPARAM lParam ){ SetWindowWord( hWnd, 0, pWBCreating );
    SetWindowLong( hWnd, GWL_WNDPROC, (LONG)WinBase::AllWndProc );
    pWBCreating->hWnd = hWnd; return pWBCreating->WndProc( uMessage,
    wParam, lParam ); }

This results in the first message to each window instance being handled by WinBase::FirstWndProc() which sets our pointer and sets the instance's copy of the window handle. The window then has its window procedure changed to AllWndProc for all future messages. Finally the virtual WndProc() function is called for the class instance.

The WinBase::WndProc() function could call DefWindowProc(), thus making it equivalent to the base window class as provided by the system.

This scheme achieves the association of window messages with our own data objects but still leaves the derived WndProc() implementation to crack the messages with a large switch - case. As this is one of the most criticised aspects of conventional Windows programming it would be nice to do this in some cleaner way.

Message cracking one way

In practice, messages can be divided into two categories - commands and everything else. So we could hive off commands and have a separate virtual function for them:

virtual void DoCommand( WPARAM idItem, HWND
    hWndCtrl, WORD wNotify );

This discards the WM_COMMAND part of the message as it is implicit and splits the lParam into its components for a command.

Other messages can be treated in a similar way. Some are so important that they necessarily deserve functions of their own. Particular examples are WM_CREATE and WM_PAINT, prototypes could be:

virtual LRESULT DoCreate(); virtual
    LRESULT DoPaint( PAINTSTRUCT& ps );

Although the WM_CREATE message has a pointer to CREATESTRUCT in the lParam, this isn't likely to be needed in this circumstance, after all it's our CREATESTRUCT! For the WM_PAINT message, AllWndProc() can call BeginPaint() - EndPaint() around the virtual function call.

Other messages such as client and non client mouse messages come in convenient groups and these could also have functions of their own:

virtual LRESULT DoMouse(
    UINT iMessage, WPARAM wKeyFlags, int x, int y ); virtual LRESULT
    DoNcMouse( UINT iMessage, WPARAM wKeyFlags, int x, int y
    );

Obviously you will need one general purpose message handler for all those messages not covered already.

Message cracking another way

The current consensus (partly led by MFC and OWL) is that each message should be mapped to handler functions one to one. This also applies to commands.

One way that this could be done is to declare one virtual function for each message, the problem with this is that each derived class would need a huge virtual method table (compared to most VMTs that is). The job of deciding which function to call remains difficult for the static window procedure (it could be a very long switch - case). A natural solution would be to design a cunning hashed dictionary (as above when mapping window handles to class pointers).

In practice the hashed map is used but without the virtual functions, after all if you're going to the trouble of writing the hashed map then you can just call the functions directly!

I have to admit that although this mechanism is quite efficient I find it less elegant than the partial cracking described above. However I'm probably in a minority of one on this issue.

If it was that easy...

Sadly, the above remains an over simplification of the issues but I don't think I could get away with another ten thousand words. The discussion so far raises more questions than it answers but that's programming for you.

Free Software

With the kind permission of Pat Fuller, I'm making available a small class library that we wrote some time ago that can be used to build simple SDI and MDI applications. I think that it may be of use to help in understanding some of the concepts that I've been covering. Some aspects of it may form the basis of a future article.

It isn't in competition with MFC which seems to have become the dominant Windows specific class library. We wrote it with the idea of making it as simple and efficient as possible. I have only used it for a couple of serious jobs since but I was thinking of resurrecting it and extending it in various ways. If anyone else is interested then please contact me.

Notes: 

More fields may be available via dynamicdata ..