Menu

Understanding_the_Response_Tables_in_ObjectWindows

Understanding the Response Tables in OWL

In this article we'll take a closer look at how the response table macros enable a class to work with the OWL message-dispatching mechanism. We'll examine each macro individually, and then we'll show you how OWL uses the response table at runtime.


Changes to response tables in OWLNext 6.40

A major overhaul of the Windows message dispatch was introduced in version 6.40. It simplifies the implementation of response tables a great deal by eliminating the signature templates altogether, as well as the use of undefined casts, thus making the code C++ standard conformant with regard to defined behaviour. For more formation about these changes, see Windows Message Dispatch in OWLNext.

The rest of this article describes the OWL 5 implementation of response tables.

Expanding the response table macros

Assume that we have derived a new class TMsgWindow from the OWL class TWindow as follows:

class TMsgWindow
: public TWindow
{
public:
  TMsgWindow(TWindow* parent = 0);

protected:
  void EvLButtonDown(uint, TPoint&);

  DECLARE_RESPONSE_TABLE(TMsgWindow);
};

DEFINE_RESPONSE_TABLE1(TMsgWindow, TWindow)
  EV_WM_LBUTTONDOWN,
END_RESPONSE_TABLE;

Before the compiler begins processing this file, the preprocessor expands the response table macros. We will now look at the expanded code passed from the preprosessor to the compiler. First, here is the expansion of DECLARE_RESPONSE_TABLE:

private:
  static TResponseTableEntry<TMsgWindow> __entries[];
  typedef TResponseTableEntry<TMsgWindow>::PMF TMyPMF;
  typedef TMsgWindow TMyClass;

public:
  BOOL Find(TEventInfo&, TEqualOperator = 0);

The static array (__entries) will hold the actual response table entries. The two typedef statements create type aliases that are used in the definition of the table to generically refer to the enclosing class type and member function pointers of the corresponding type.

Here is the expansion of DEFINE_RESPONSE_TABLE1:

BOOL TMsgWindow::Find(TEventInfo& eventInfo, TEqualOperator equal)
{
  eventInfo.Object = (GENERIC*) this;
  return SearchEntries((TGenericTableEntry*) __entries, eventInfo, equal) ||
    TWindow::Find(eventInfo, equal);
}

TResponseTableEntry<TMsgWindow> TMsgWindow::__entries[] =
{
  {WM_LBUTTONDOWN, 0, (TAnyDispatcher)::v_U_POINT_Dispatch, (TMyPMF)v_U_POINT_Sig(&TMyClass::EvLButtonDown)},
  {0, 0, 0, 0}
};

As you can tell, the response table macros are easy to use, but they do some complex things. Let's take a closer look at what is going on here.

Declaring the response table

When you add the macro instantiation DECLARE_RESPONSE_TABLE to the TMsgWindow class, it expands to declare a static array of TResponseTableEntry <TMsgWindow> objects. The preprocessor nests this response table inside the TMsgWindow class as a private static member variable. This prevents any other classes or functions from accidentally using this class' response table. The TResponseTableEntry class template that appears within the macro expansion is defined in the include file "eventhan.h" (added indirectly via "applicat.h"). The class template definition looks like this:

template <class T>
class TResponseTableEntry
{
public:
  typedef void (T::*PMF)();

  union
  {
    uint Msg;
    uint NotifyCode;
  };
  uint Id;
  TAnyDispatcher Dispatcher;
  PMF Pmf;
};

The compiler will replace the T parameter with the TMsgWindow class name when this template is instantiated. Because this is a class template, the compiler doesn't actually create the class until it scans the declaration of the TMsgWindow class, where a class template instantiation is made.

If you look closely, you'll notice a few unusual things about this class. The first oddity is the typedef statement that declares PMF as a pointer-to-member function. Pointer-to-member functions are a type-safe method of calling a member function at runtime without knowing its name. By defining it within a generic class template, the OWL framework can use a PMF pointer-to-member function to call an event-handling function in classes you define. As a result of the typedef statement for the pointer-to-member function, the data member Pmf becomes a pointer-to-member function of your class (in this case TMsgWindow).

The other data members represent the contents of the entry: a message identifier (Msg) or notification code (NotifyCode) for which the entry handles messages, a message source identifier (Id) which acts as a filter (if appropriate), a function pointer (Dispatcher) that points to one of the OWL dispatcher functions (which decodes the message arguments), and finally the member function pointer (Pmf) pointing to the desired handler.

Initializing the response table

The macro EV_WM_LBUTTONDOWN macro instantiation creates the first entry in the table. The initializer is equivalent to the following assignments (ignoring all those ugly casts):

TMsgWindow::__entries[0].Msg = WM_LBUTTONDOWN;
TMsgWindow::__entries[0].Id = 0;
TMsgWindow::__entries[0].Dispatcher = ::v_U_POINT_Dispatch;
TMsgWindow::__entries[0].Pmf = v_U_POINT_Sig(&TMsgWindow::EvLButtonDown);

The first three elements of the entry do not depend on the handler class, and are initialized with the same values that would be used for any other class that responds to a WM_LBUTTONDOWN message. The initialization of Pmf, though, requires some explanation.

When the compiler scans the line that contains the expression &TMsgWindow::EvLButtonDown, it calculates the location of the function EvLButtonDown in the TMsgWindow class (this location is a pointer-to-member function). Next, the compiler sees this pointer as an argument to the function v_U_POINT_Sig function. The header file "signatur.h" defines this function template so it takes a pointer-to-member function as an argument to the function template.

The pointer that the macro places in this function call must point to a function that has an unsigned integer (UINT) and a TPoint object as parameters and returns void. In fact, the name of the function template tells you what type of pointer it will accept. In the name v_U_POINT_Sig, the part v means the function returns void, U means the function expects an unsigned integer argument, and POINT means the second argument must be a TPoint object.

The purpose of calling the v_U_POINT_Sig template function is to confirm that the signature of your class' response function is correct for responding to a WM_LBUTTONDOWN message. The return value from this template function is simply the pointer to the member function EvLButtonDown itself.

Finally, the compiler scans the expression (TMyPMF)v_U_POINT_Sig(). This statement casts the pointer to the EvLButtonDown member function to the more generic type TMyPMF declared in TResponseTableEntry. While ugly and not C++ standard compliant, casting the pointer this way allows the Pmf data member in the response table entries to hold pointers to member functions with different signatures. After casting the member function pointer, the compiler assigns the Pmf data member with it.

Ending the response table definition

The macro END_RESPONSE_TABLE adds a null sentinel entry as the final element of the response table array. This allows response tables to be otherwise empty (without the sentinel and no other elements, the compiler would have complained about trying to create an empty array). The hidden sentinel entry means that you have to put a comma after every entry, even your last one.

The response table in action

Now, let's consider how the response table works at runtime when TMsgWindow receives a WM_LBUTTONDOWN message. Windows will dispatch the message in the application's message loop by calling the window procedure for the interface object (the HWND instance). The window procedure (a "thunk" specially created for our TMsgWindow instance) will forward the message to TMsgWindow::WindowProc, which through various dispatch logic will end up calling the macro-created virtual member function override TMsgWindow::Find. This function in turn calls the function SearchEntries (inherited from the class TEventHandler) to look through the response table, searching for an entry that has the same message or notification code, and the same source identifier (if applicable), as the current message.

WM_LBUTTONDOWN dispatched to TMsgWindow::EvLButtonDown

If SearchEntries finds a matching entry, it adds a pointer to that entry in the TEventInfo object argument that OWL maintains for the current Windows message. Otherwise, TMsgWindow::Find forwards the search to the immediate base class of the TMsgWindow class, TWindow.

However, if there is a matching response table entry for the current message, the Dispatch function of the owner window uses the Dispatcher in this entry to call the correct message decode function; in this case v_U_POINT_Dispatch. The Dispatcher cracks the message into the appropriate arguments and then forwards the call to the given member function Pmf.

For the TMsgWindow class, cracking the WM_LBUTTONDOWN message merely involves casting the LPARAM parameter as a TPoint object and then passing the TPoint argument and the WPARAM argument to the member function TMsgWindow::EvLButtonDown by dereferencing the Pmf member function pointer, bound to the instance of our class TMsgWindow. Finally, EvLButtonDown executes with the given TPoint and WPARAM arguments.

Conclusion

The OWL macros that create message response tables can be a little confusing because they shield you from the complexity of the message-cracking and dispatching process. As you can tell, using the response table macros saves you from having to enter some very complex code. Since the original OWL 5, and since the original version of this article, the C++ language and Windows have evolved, and OWLNext has evolved with them. To support 64-bit Windows and to improve standard C++ compliance, the underlying implementation described here has been dramatically overhauled.

For more information, see Windows Message Dispatch in OWLNext.

PS. Using modern C++, much of the complexity described here can, and have been, encapsulated using pure C++ without macros, while retaining most of the ease-of-use and brevity of the macro approach. See Response tables without macros.


Related

Wiki: Enabling_and_disabling_commands_in_an_ObjectWindows_application
Wiki: Knowledge_Base
Wiki: Responding_to_standard_messages_with_ObjectWindows
Wiki: Response_tables_without_macros
Wiki: Windows_Message_Dispatch_in_OWLNext

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.