Message Cracker System Complete Documentation

This describes the RatWin/Tomahawk system of extracting meaningful parameter information from the message information passed to a WinProc.


Related Documents


The Win32 GUI API, like the 16-bit Windows before it, calls the WinProc with 4 parameters. It uses the same four parameters for any type of message, and the relevant information is packed, sometimes quite creatively, into those values.

Many years ago there was a header file in the Windows SDK called WINDOWSX.H that had macros called “message crackers”. Before Wizard-driven frameworks became popular, this was very popular among writers who would teach Windows programming.

In the Repertoire library, the central idea is to make code that is truely reusable on the binary level, not wizards that generate stock code. The mechanism in Tomahawk for building message handlers uses a virtual function to handle whole groups of messages, as opposed to one virtual function for every possible message. The plumbing of the message router is exposed, at least a little bit, to the user. So, breaking the raw message parameters up into something useful and appropreate to the message canít be hidden away somewhere.

To this end, I wanted something like the old Message Crackers. Only, do so without macros, and with as much type-safety as practical.

The resulting system is centered around the Message Cast mechanism.

The sMSG structure

The sMSG structure, defined in Ratwin\message=struct.h, contains the data that is passed to the WinProc functions. The fields are named hwnd, message, wParam, and lParam.

This is designed to have the same layout on the stack, when passed by value, as the four invidual parameters normally have in a WinProc. Specifically, you can define a WinProc to receive a sMSG and Windows indeed passes you one!

Why pass a structure?

There are several motives behind passing a single structure rather than 4 individual parameters. Originally, it tied in with the concept behind the thunk class which, at the time, only handled one parameter. This is really all that is needed, because multiple parameters can always be combined into a single structure. This is no longer an issue because there is now a distinct template for closures with each number of arguments. However, what started out as a limitation/work-around turned into an inspiration. It was better as a structure than it ever was as four loose arguments!

It makes sence to combine the parameters that are always passed as a group. It makes engineering sence that these ought to be grouped together into one named entity, and it is more efficient to pass the whole structure (by reference) to deeper functions rather than re-pushing the same parameters over and over. The code handling messages will often contain only a little code and then call another function with the same message data, and call the base class version, and call the default, etc. The message is routed to modular code, so it gets passed a lot and treated as a lump.

Finally, putting the items together makes the idea of the MSG_cast very clean and elegant. In comparison, it would be awkward to deal with loose parameters.

Why is it so plain?

Iíve seen a structure that uses unions to allow easy addressing of the individual words and bytes of the lParam and wParam, and provide meaningful names for the commonly-used fields, all together in one structure and referring to the same bytes. This kind of system was part of the inspiration, but in C++ we can do much better than we could in C, and expect a higher degree of type safety.

So, the sMSG structure doesnít have any of that baggage in it. Itís just the 4 parameters with no special way to “crack” them or refer to them with more meaningful names. Instead, each set of cracked arguments is described in a different class, with each class named after the message it services.

WM_..._msg structures

There are structures named after the messages they service, one per file, in the Tomahawk\MSG directory. For example, WM_command_msg is a struct in Tomahawk\MSG\WM_COMMAND.h, and WM_MOUSEMOVE_msg is in Tomahawk\MSG\WM_MOUSEMOVE.h.

Each struct defines appropriate data members instead of wParam and lParam. For example, WM_MOUSEMOVE_msg contains an unsigned KeyIndicators and two shorts for XPos and YPos. You donít have to know implicitly that the key indicators are in wParam, since it is called KeyIndicators instead. You donít have to deal with unpacking the high and low bits from lParam since the struct is declared in a way to access those bits directly. Generally, the data member names are those used in some versions of the Windows SDK documentation of the message.

The data members in the typed message structure are better than knowing how to unpack and extract the values from the generic sMSG. We could stop there. But, Tomahawk goes a step further and defines inline accessor functions for all the information.

In the WM_MOUSEMOVE_msg struct, the const member function key_indicators() might seem to be no different than the data member KeyIndicators. You can in fact use either, since all the data members are public. However, notice that the function returns an enumeration for the flags, while the data member is a simple unsigned. The data member could not be declared as the enumeration type because there is no way to specify the actual bit-size and control the layout. So the data members are defined using basic integer types only, while the member functions can be defined with stronger typing. The functions are more flexible, and there could be multiple ways to refer to the same data, and no need to match the actual concrete data layout. In short, the functions provide the kind of abstraction you would normally expect from software engineering, while the data members match the pre-defined bit layout.

Also, the member functions are given consistant and unambiguous names across all message types. mouse_position is clearer than just pos.

Message Casting

The specific message structure contains only those things for that message, and exposes them in a normal enough manner. The question is, how do you get a specific message structure, when WinProc is passed a generic sMSG?

All the message structures are exactly the same size, since they all expose the same sized record in different ways. So, it should be possible to cast one to another. Given a pointer to a struct, you can cast it into a pointer to a different struct. Given a value (as opposed to a pointer) you can cast it to a reference to some other struct. Using old-style C casts, the compiler will take your word for it and never complain.

Here we have a slightly better way. In C++ the new-style casts are better because each one has a different purpose, and the compiler can catch some kinds of mistakes. In Tomahawk there is a MSG_cast that matches the syntax of the C++ casting operators. It is more limited than a C-style cast in that the compiler ensures that only different kinds of message structures are cast to other message structures.

That is, you could code process_Size (MSG_cast<WM_SIZE_msg>(msg)); and the compiler makes two checks: it checks that process_Size is expecting a WM_SIZE message, and it also checks that msg is some message structure. You canít pass just anything to a message cast—it only works between message structures, and this is a compile-time check.

What it doesnít do is make sure that the msg is in fact a WM_SIZE message. Since it is typically used like this:

Listing 1

 switch (msg.message) {
    case WM_MOUSEMOVE:
       process_MouseMove (MSG_cast<WM_MOUSEMOVE_msg>(msg));
    case WM_SIZE:
       process_Size (MSG_cast<WM_SIZE_msg>(msg));

misusing it is generally not a problem. However, thereís nothing to stop you from writing

Listing 2

void C::process_Size (const WM_SIZE_msg& msg)
 assert (msg.message == WM_SIZE);
 // ... get on with the real work.

just to make sure. Given the tight naming convention between the message name and the struct name, making the structure define its own id would not be generally helpful for coding. But, what about templates? To make it possible to make generic code like:

Listing 3

template <typename T>
void foo (const T& msg)
 assert (msg.message == T::message_constant);
 // ...

The example program message_tap_demo.cxx shows how to use the message casts for message cracking. The main handler takes its sMSG and casts it to the proper type, calling the handler function for that message type. The handlers declare their parameter of the specific type, and pass by reference.