Understanding the Response Tables in ObjectWindows.

In the article Responding to standard messages with ObjectWindows,
we tell you how to derive a new class from the ObjectWindows Library (OWL) TWindow
class that responds to the Windows message WM_LBUTTONDOWN. To make the new class
work correctly, we used a number of OWL macros to create a Response Table.
Here, we'll take a closer look at how these 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.
Expanding the Macros
Listing A in Responding to standard messages with ObjectWindows
shows you how to derive the TMsgWindow class from the OWL class TWindow.
In this article, Figure A shows the declaration for the TMsgWindow class from
that listing and the corresponding Response Table macros that follow the declaration.

Figure A - The Response Table macros keep the class declaration fairly simple.
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;

However, before the compiler begins processing this file, the preprocessor expands the
Response Table macros in some surprising ways. Figure B on the next page shows the changes
that the preprocessor makes to this code before it passes the code to the compiler.

Figure B - After the compiler expands the macros, the TMsgWindow class becomes
more complex.
class TMsgWindow : public TWindow
{
public:
TMsgWindow(TWindow* parent = 0);
protected:
void EvLButtonDown(uint, TPoint&);
// Beginning of DECLARE_RESPONSE_TABLE expansion
private:
static TResponseTableEntry<TMsgWindow>
__entries[];
typedef TResponseTableEntry<TMsgWindow>::PMF
TMyPMF;
typedef TMsgWindow
TMyClass;
public:
BOOL Find(TEventInfo&, TEqualOperator = 0);
// End of the DECLARE_RESPONSE_TABLE macro
};
// Beginning of DEFINE_RESPONSE_TABLE1 expansion
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[] =
{
// End of DEFINE_RESPONSE_TABLE1 macro
// Beginning of EV_WM_LBUTTONDOWN
{
WM_LBUTTONDOWN, 0,
(TAnyDispatcher)::v_U_POINT_Dispatch,
(TMyPMF)v_U_POINT_Sig(
&TMsgWindow::EvLButtonDown)
}
// End of EV_WM_LBUTTONDOWN
, // Comma after EV_WM_LBUTTONDOWN
// Beginning of END_RESPONSE_TABLE
{0, 0, 0, 0}
}
// End of END_RESPONSE_TABLE
; // Semicolon after END_RESPONSE_TABLE

As you can tell, the Response Table macros are easy to use, but they do some complex
things. To get a feel for what's going on, let's look at the expansion of each macro
individually.
Declaring the Response Table
When you add the DECLARE_RESPONSE_TABLE macro to the TMsgWindow
class, it expands to declare a static array of
TResponseTableEntry<TMsgWindow>
objects. The preprocessor nests this template class inside the TMsgWindow
class. This prevents any other classes or functions from accidentally using this class's
Response Table.
The EVENTHAN.H include file (added indirectly via APPLICAT.H) declares the TResponseTableEntry
template class that appears within the macro expansion. The class declaration looks like
template <class T> class TResponseTableEntry {
public:
typedef void (T::*PMF) ();
union {
uint Msg;
uint NotifyCode;
};
uint Id;
TAnyDispatcher Dispatcher;
PMF Pmf;
};
Later, the compiler will replace the class T parameter with the TMsgWindow
class name. To the compiler, this new template class will look like
class TResponseTableEntry<TMsgWindow> {
public:
typedef void (TMsgWindow::*PMF) ();
union {
uint Msg;
uint NotifyCode;
};
uint Id;
TAnyDispatcher Dispatcher;
void (TMsgWindow::* Pmf) ();
};
Because this is a template class, the compiler doesn't actually create the declaration
until it scans the declaration of the TMsgWindow class.
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 typesafe method of calling a member function
at runtime without knowing its name.
By including it in a 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 Message type or Notification Code that Windows
sent to this window, a resource ID (if appropriate), and a function pointer that points to
one of the OWL Dispatcher functions. The unnamed union that holds the Message type or
Notification Code is an anonymous union. You can use either name with the syntax
aResponseTableEntry.Msg
aResponseTableEntry.NotifyCode
Finally, the DECLARE_RESPONSE_TABLE macro adds two typedef statements
that the template class TResponseTableEntry<> will use, and the macro
declares an override of the virtual function Find() that TMsgWindow
inherits from the TWindow class. The typedef statements allow the TResponseTableEntry<>
class members to point to TMsgWindow member functions. The DEFINE_RESPONSE_TABLE
macro will implement the Find() function.
Defining the Response Table
Next, we use some more OWL macros to create the Response Table. In Figure B, you'll see
that the DEFINE_RESPONSE_TABLE1 macro implements the TMsgWindow::Find()
function.
The DEFINE_RESPONSE_TABLE macro completes its work by beginning the definition
of __entries, a static array consisting of TResponseTableEntry<TMsgWindow>
objects. The remaining macros complete this array's definition and initialization.
Initializing the Response Table
To create the entries in the Response Table, the EV_WM_LBUTTONDOWN macro
begins by initializing the first item in a static TResponseTableEntry<>
array for TMsgWindow. This array is the actual Response Table.
If you aren't familiar with initializing structure or class arrays with this syntax,
it's functionally equivalent to
TMsgWindow::__entries[0].Msg =
WM_LBUTTONDOWN;
TMsgWindow::__entries[0].Id = 0;
TMsgWindow::__entries[0].Dispatcher =
::v_U_POINT_Dispatch;
TMsgWindow::__entries[0].Pmf =
TMsgWindow::EvLButtonDown;
The EV_WM_LBUTTONDOWN macro defines the first three elements of the entry with
the same values it would use 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 statement
&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 v_U_POINT_Sig() function. The SIGNATUR.H header
file in the BC4\INCLUDE\OWL directory 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
takes an unsigned integer (UINT) and a TPoint object as arguments 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, 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's 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 statement (TMyPMF)v_U_POINT_Sig(). This
statement casts the pointer to the EvLButtonDown() function as a member function
of the TResponseTableEntry<TMsgWindow> class (TMyPMF). 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.
You must include a comma after the EV_WM_LBUTTONDOWN macro. This comma
separates the entry in the array's initialization list from the subsequent entries.
Ending the Response Table definition
The END_RESPONSE_TABLE macro adds a null entry as the second element of the
Response Table array. Table A shows the elements of the TMsgWindow class's
Response Table after the compiler expands the macros and initializes the array.
Watching The Response Tables in Action
Now, let's see how the expanded macros behave at runtime. Figure C graphically
illustrates the function calls, which we'll mention by reference number (1).
Figure C - The Response Table macros implement a complex series of function
calls.
To pass a message to the TMsgWindow object, the owner TFrameWindow
object calls the macro-created TMsgWindow::Find() function (1). This function in
turn calls the function SearchEntries() (inherited from the class TEventHandler)
to look through the Response Table for an entry that has the same Message type as the
current message (2).
Table A The Response Table for the TMsgWindow class has only one entry.
| Message or Notify ID |
Resource ID |
Dispatcher function for message |
Pointer to a TMsgWindow member function |
| WM_LBUTTONDOWN |
0 |
v_U_POINT_Dispatch |
&TMsgWindow::EvLButtonDown |
| 0 |
0 |
0 |
0 |
If the SearchEntries() function finds a matching entry, it adds a pointer to
that entry in the TEventInfo object that OWL maintains for the current Windows
message. If the SearchEntries() function can't find a matching entry in the TMsgWindow
class's Response Table, the macro-created TMsgWindow::Find() function calls the Find()
function of the immediate base class of the TMsgWindow class, TWindow
(3).
However, if there's a matching Response Table entry for the current message, the
Dispatch() function of the owner window uses the Dispatcher data member in this entry to
call the correct Dispatcher function this case v_U_POINT_Dispatch() (4). The
Dispatcher function cracks the message into the appropriate component types and then calls
the correct member function.
For the TMsgWindow class, cracking the WM_LBUTTONDOWN message merely
involves casting the LPARAM argument as a TPoint object and then passing
the TPoint argument and the WPARAM argument to the member function EvLButtonDown()
from the TMsgWindow class by using the TMyPMF member function pointer
(5). Finally, the EvLButtonDown() function executes by using the TPoint
and WPARAM arguments.
As you can tell, using the Response Table macros saves you from having to enter some
very complex code. Unfortunately, you can still have problems if you aren't careful. See
the accompanying article, Common problems
when using the Response Table macros below, for more information.
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. However, once you examine the logic beneath the macros as we've done
here, you'll understand the special calling syntax they require. |