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 MacrosListing 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 TableWhen 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 TableNext, 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 TableTo 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 definitionThe 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 ActionNow, 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.
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. ConclusionThe 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. |
|