A Callback Class

                                                                                                                                       Doug Young









This paper briefly describes an alternate approach for using C++ member 
functions with Xt callbacks. This material was originally written as an 
appendix to Object-Oriented Programming with C++ and OSF/Motif f, 
Prentice Hall, 1992, but was cut from the final manuscript in the interest of 
simplicity and consistency. 







The approach used throughout Object-Oriented Programming with C++ and OSF/Motif for dealing 
with member functions and widget callbacks requires two functions. One function is declared as a 
static member function and registered as a Motif callback using XtAddCallback(). An object's 
this pointer is provided as client data when the callback is registered. When this static member 
function is called, it retrieves the instance pointer from its client data argument and calls the second 
function, which can be a normal member function, relative to that object. 

This approach leads to many member functions that are very similar. For example, almost every 
callback function looks like:

void SomeClass::memberFunctionCallback ( Widget, 

                                           XtPointer clientData, 

                                           XtPointer )

  {

      SomeClass *obj = (SomeClass *) clientData;

      obj->memberFunction();

  }



The only difference between various static functions used as callbacks is the class to which the 
function belongs, and the exact member function being called. The form is identical in all cases

Because the form of all these functions is so similar, it is possible to hide some of the details of 
this approach. One way to do this is to capture the entire mechanism in a C++ class. This paper 
examines an implementation of such a class, the Callback class.

The Callback class has some similarities to the Cmd class described in Chapter 7 of Object-
Oriented Programming with C++ and OSF/Motif. However, the implementation is a bit different. 
Basically, every class that wants to use a Callback object must create a new derived class that 
overrides an execute() member function declared by the Callback class. This member function 
is called by a static member function, registered by the Callback base class as the actual callback 
function. The execute() function is expected to call a normal member function. 

This scheme requires only a single static member function per class, and only a single 
execute() member function. The technique allows normal member functions to be registered as 
pseudo-callbacks. However, the approach adds one more level of indirection than the alternate 
approach.

Let's start by looking at the declaration of the Callback class. The entire mechanism is imple-
mented in a header file, using macros and inline functions. The file Callback.h is written as follows:

1  ////////////////////////////////////////////////////////

2  // Callback.h: A class that encapsulates callbacks

3  ////////////////////////////////////////////////////////

4  #ifndef CALLBACK_H

5  #define CALLBACK_H

6  

7  #include <Xm/Xm.h>

8  #include <generic.h>

9  

10  class Callback {

11  

12    protected:

13  

14      Widget    _w;          // Widget for which callback is registered

15      String    _name;       // The callback name, 

16                             // needed to remove callback

17      XtPointer _clientData; // Xt's idea of client data is used

18                             // for the this pointer,

19                             // so replace it with our own.

20   

21      // Constructor registers callback and saves other info

22      // that might be needed later as data members

23  

24      Callback ( Widget w, String name, XtPointer clientData )

25      { 

26  	        XtAddCallback ( w, 

27  		                        name, 

28  		                        &Callback::callbackStub, 

29  		                        (XtPointer) this );

30  	        _w          = w;

31  	        _name       = name;

32  	        _clientData = clientData;

33      }

34  

35      // Destructor removes the callback, using save data members

36  

37      ~Callback()

38      { 

39          XtRemoveCallback ( _w, 

40                             _name, 

41                             &Callback::callbackStub, 

42                             (XtPointer)this );

43      }

44   

45      // Execute must be overriden by derived classes

46  

47      virtual void execute ( Widget, XtPointer, XtPointer ) = 0;

48  

49    private:

50  

51      // Generic callback function works for all callbacks

52   

53      static void callbackStub ( Widget    w, 

54                                 XtPointer clientData, 

55                                 XtPointer callData )

56      {

57          	Callback *obj = (Callback *) clientData;

58  	        obj->execute ( w, obj->_clientData, callData );

59      }

60  };

The Callback class declares three protected data members, the widget with which the callback 
is registered, the name of the callback, and a pointer that can be used to point to some arbitrary data. 
Recall that the static member function scheme uses the client data supported by 
XtAddCallback() to pass the this pointer to the callback function. Although this scheme still 
needs to have the this pointer, wrapping the callback in a class hides the process and provides a 
place to keep some additional data on a per callback basis. This allows us to make the client data 
argument available to programmers again.

The Callback constructor expects a widget, the name of a callback and a pointer to any client 
data. It registers callbackStub(), which is a static member function defined by the Callback 
class, as the named callback function for the given widget. The Callback constructor also retains the 
widget, the name, and the client data as data members in the Callback object.

The destructor is very simple. It uses the callback name and widget kept in the object and calls 
XtRemoveCallback() to remove the callback registered in the constructor.

The function callbackStub() is registered for all callbacks, regardless of the type. It is 
declared as an inline static member function, and looks much like the other callback function used 
throughout this book. It retrieves the this pointer provided as client data when the callback was 
registered, casts it to the appropriate type, and calls the execute() member function for this 
instance. Because all callbacks use this same member function, the type of the object passed as client 
data will always be Callback. This makes this approach slightly more type safe than the alternate 
approach, in spite of the cast, which is still needed.

The execute() member function is declared as a pure virtual function, and must therefore be 
defined by derived classes. This function calls the member function registered as the pseudo-
callback.

Now lets' take a first look at how this class is used. The Callback class cannot be used directly. 
A derived class must be created from every class that needs to register callbacks. It is easiest to just 
look at an example. The following ControlCallback class could be used with the Control class 
described in Chapter 2 of Object-Oriented Programming with C++ and OSF/Motif. We could 
declare the ControlCallback class as follows:

/////////////////////////////////////////////////////////////

  // ControlCallback.h: Callback class to be used with Control

  //////////////////////////////////////////////////////////////

  	#ifndef CONTROLCALLBACK_H

  	#define CONTROLCALLBACK_H

  

  	#include "Callback.h"

  

  	// Define ControllPMF as a type

  

  typedef void ( Control::*ControlPMF  )( Widget, XtPointer, XtPointer ); 

  

  // Declare the ControlCallback class as a derived calss of Callback

  

  class  ControlCallback : public Callback {

  

    public:	

  

     // Constructor takes a pointer to an instance of Control,

     // and a pointer to a Control member function, in addition

     // to the arguments requires by the Callback constructor

  

      ControlCallback  ( Control    *obj,  

                         ControlPMF  pmf, 

                         Widget      w, 

                         String      callbackName, 

                         XtPointer   clientData ) :

                                Callback ( w, callbackName, clientData )

      { 

          _obj = obj; // Keep the instance pointer around

          _pmf = pmf; // Remember the member function pointer

      }

  

    protected:	

  

      Control     *_obj;  // Stores a Control object

  	    ControlPMF   _pmf;	  // A pointer to a Control member function

  

    private:	

  

      // execute() is called from Callback class, indirectly

      // when callback is invoked. Use the stored Control instance

      // to call the member function. Pass the widget, calldata

      // and client data, as stored in this object.

  

      virtual void execute ( Widget w, 

                             XtPointer clientData,

                             XtPointer callData )

      {

           ( _obj->*_pmf )( w, clientData, callData ); 

       } 

  };

    

To use this class, we need to instantiate a ControlCallback object with the appropriate arguments 
for each desired callback. Notice that only one ControlCallback class is needed regardless of how 
many widgets or how many different member functions are supported by the Control class. 

However, notice that this ControlCallback class can only be used with the Control class. Using 
this technique with other classes would require a new derived class, with the appropriate members 
defined to point to instances of each new class. 

This seems like a lot of work, just to avoid writing a simple static member function. Fortunately, 
we can use a simple technique to reduce the amount of work require down to a single line of code. 
Let's return to the file Callback.h and look at the following macro, which is defined following the 
Callback class declaration:

61   #define Callbackdeclare(CLASS)						                               \

62   class CLASS;								                                                 \

63                                                                \

64   typedef void (CLASS::*name2(CLASS,PMF))( Widget,             \

65                                            XtPointer,          \

66                                            XtPointer );        \

67                                                                									\

68   class name2(CLASS,Callback) : public Callback {			 	             \

69                                                                \

70    public:						 	                                                    \

71                                                                \

72      	name2(CLASS,Callback)( CLASS* obj,                        \

73  			                           name2(CLASS,PMF) pmf,              \

74  			                           Widget w,                          \

75  			                           String callbackName,               \

76                             XtPointer clientData ) :           \

77  					                  Callback ( w,                               \

78  						                             callbackName,                    \

79  						                             clientData )                     \

80      {                                                         \

81      _obj = obj;                                                \

82      _pmf = pmf;                                                \

83   }                                                             \

84                                                                 \

85    protected:								                                                   \

86                                                                 \

87      	CLASS* _obj;					                                               \

88      	name2(CLASS,PMF) _pmf;					                                     \

89                                                                 \

90    private:                                                     								\

91                                                                 \

92  	    virtual	 void execute ( Widget w,                           \

93  			                           XtPointer clientData,               \

94  			                           XtPointer callData )                \

95  	{                                                              \

96  	        ( _obj->*_pmf )( w, clientData, callData );            \

97      }                                                          \

98  	};                                                             \

99                                                                 \

100   name2(CLASS,Callback) *addCallback ( Widget w,               \

101                                        String name,            \

102                                        name2(CLASS,PMF) pmf,   \

103   		                                     XtPointer clientData )  \

104   {                                                            \

105  	     name2(CLASS,Callback) *cb =                              \

106  	                    new name2(CLASS,Callback)( this,          \

107                                                 pmf,           \

108                                                 w,             \

109                                                 name,          \

110                                                 clientData );  \

111  	    return cb;                                                \

112   }

113  #endif

This macro captures the format of the ControlCallback class we just discussed. This macro uses 
the name2() macro found in the file generic.h to concatenate two symbols together to generate one 
new symbol. If we were to process the statement

CallbackDeclare(Control)

through the C++ preprocessor, the result would be approximately the same as the ControlCallback 
class implemented earlier. There is one additional feature of this macro, the addCallback() 
function defined starting on line 100. This is a convenience function that instantiates a Callback 
object and provides the this pointer to the constructor. 

This macro is intended to be included in the declaration of a class. This makes the 
addCallback() function a member function of the declaring class.

With this macro defined, it is very easy to use the Callback class. Let's look at how it could be 
used with the Control class described in Chapter 2 of Object-Oriented Programming with C++ and 
OSF/Motif. The class declaration is nearly identical to the declaration in that book, except that the 
class has no static member functions, and the Callbackdeclare macro is included inside the 
private portion of the class. Notice that the stop() and start() member functions support the 
arguments expected by the Callback class as well.

1   ////////////////////////////////////////////////////////////////////

2   // Control.h: A start/stop pair of buttons for the stopwatch program

3   ////////////////////////////////////////////////////////////////////

4   #ifndef CONTROL_H

5   #define CONTROL_H

6   #include "BasicComponent.h"

7   #include "Callback.h"

8  

9   class Timer;

10  class Stopwatch;

11  

12  class Control : public BasicComponent {

13  

14    private:

15  

16      void start ( Widget, XtPointer, XtPointer );  

17      void stop ( Widget, XtPointer, XtPointer ); 

18  

19      Callbackdeclare(Control)

20  

21   protected:

22  

23      Timer     *_timer;       // The timer controlled by this class

24      Stopwatch *_stopwatch;   // The stopwatch containing this control

25      Widget     _startWidget; // The start button

26      Widget     _stopWidget;  // Stop button

27  

28    public:

29  

30      Control ( Widget, char * , Stopwatch *, Timer * );

31  };

32  #endif

The Control constructor is much the same as before. However, this version registers normal 
member functions as callbacks using the addCallback() member function generated by the 
Callbackdeclare() macro. Notice that the client data is specified as NULL in this case, and 
that no this pointer is required.

1  //////////////////////////////////////////////////////////////////

2  // Control.C: A start/stop pair of buttons for the stopwatch program

3  ///////////////////////////////////////////////////////////////////

4  #include "Control.h"

5  #include <Xm/Xm.h>

6  #include <Xm/RowColumn.h>

7  #include <Xm/PushB.h>

8  #include "Timer.h"

9  #include "Stopwatch.h"

10  

11  Control::Control ( Widget    parent, 

12                     char      *name, 

13                     Stopwatch *stopwatch,

14                     Timer     *timer ) : BasicComponent ( name )

15  {

16  

17      _timer = timer;  // Keep a pointer to the timer.

18  

19      _stopwatch = stopwatch;

20  

21      // Create the component's widget tree

22  

23      _w = XmCreateRowColumn ( parent, _name, NULL, 0 );

24  

25      _startWidget = XtCreateManagedWidget ( "start",

26                                             xmPushButtonWidgetClass, 

27                                             _w,  NULL, 0 );

28      _stopWidget = XtCreateManagedWidget ( "stop", 

29                                            xmPushButtonWidgetClass, 

30                                            _w, NULL, 0 );

31  

32      // Register callbacks, specifying the object's instance 

33      // pointer as client data.

34  

35      addCallback ( _startWidget, 

36  		                  XmNactivateCallback, 

37  		                  &Control::start,

38  		                  NULL );

39  

40      addCallback ( _stopWidget, 

41  		                  XmNactivateCallback, 

42  		                  &Control::stopCallback,

43  		                  NULL );

44  

45  }

The stop() and start() member functions are written the same as they were previously, 
except that the functions take additional arguments, which are unused in this example.

46  void Control::start( Widget, XtPointer, XtPointer )

47  {

48      _timer->start();

49      _stopwatch->timerStarted();

50  }

51  void Control::stop ( Widget, XtPointer, XtPointer )

52  {

53      _timer->stop();

54      _stopwatch->timerStopped();

55  }

This approach offers several advantage. It is simpler to use, given the complete implementation 
of the Callback class and the Callbackdeclare macro. It is also slightly more type-safe. The 
only cast occurs in association with the clientData argument to the callbackStub() 
function. However, this is known to be a Callback object, regardless of what derived class is actually 
being used, making this cast almost completely safe.

Using the Callbackdeclare macro reduces the number of functions a programmer must 
write and relieves the tedium of defining two functions for every callback. The total number of 
functions is also reduced, since the same static member function is used for all callbacks registered 
by a given class. Finally, some may find this technique more aesthetically appealing than registering 
static member functions and dealing with the C functions directly.

Nothing comes for free, and there are a few disadvantages to this approach. First, the Callback 
mechanism adds yet one more level of indirection, requiring an additional function call for each 
callback. This is probably not objectionable in most cases.

A second problem could be more problematic in some cases. When a widget is destroyed, Xt 
removes all callbacks from the widget's callback lists. When using this approach, Xt will also 
remove the callbackStub() function registered with the widget. However, it cannot know to 
delete the Callback object. If widgets are destroyed dynamically, the implementation described 
above will continue to use memory even though the functions supported by the class will never be 
called.

It is possible to work around this problem by registering an XmNdestroyCallback function 
for each widget and each callback class, whose task it is to free the Callback class when the widget 
is destroyed. However, this adds even more to the complexity and overhead of the underlying 
mechanisms that implement this approach.

I would like to credit Anil Pal for suggesting this approach, and demonstrating an earlier 
version, from which the Callback class evolved.


