[comp.windows.x] C++ and X: Member-Functions as Callbacks

rink@tramp.colorado.edu (Jeff) (03/09/91)

Howdy.
Help requested from C++ and X gurus:


	I am trying to set up a callback for an X widget.  I would LIKE
	to have the callback be a member-function of a C++ class.
	An example:


		struct	blah
		{

			void	foo();

		}


		....


		blah	instance;


		XtAddCallback( ---- , ---- , instance.foo, ---- );


	
The problem(s):

	When I try it (as in the example), foo() is not called
	with the proper, hidden, THIS* argument.  THIS, in fact,
	points to a mess.

	I suspected that X is packing the normal callback arguments
	where they shouldn't be (w, client_data, and call_data),
	so I tried explicitly specifying the arguments:


		struct	blah
		{

			void	foo(Widget, caddr_t, caddr_t);

		}


	But it still doesn't work.  THIS still points somewhere else.


	Any ideas?  I'm sorry if this isn't more clear.  I'm rather new
	to both X and C++.



Jeff Rink
(rink@tramp.colorado.EDU)

bob@odi.COM (Bob Miner) (03/10/91)

     Howdy.
     Help requested from C++ and X gurus:

     	I am trying to set up a callback for an X widget.  I would LIKE
     	to have the callback be a member-function of a C++ class.
     	An example:

     		struct	blah
     		{
     			void	foo();
     		}

     		....

     		blah	instance;

     		XtAddCallback( ---- , ---- , instance.foo, ---- );
     	
     The problem(s):

     	When I try it (as in the example), foo() is not called
     	with the proper, hidden, THIS* argument.  THIS, in fact,
     	points to a mess.

     	I suspected that X is packing the normal callback arguments
     	where they shouldn't be (w, client_data, and call_data),
     	so I tried explicitly specifying the arguments:

     		struct	blah
     		{
     			void	foo(Widget, caddr_t, caddr_t);
     		}

     	But it still doesn't work.  THIS still points somewhere else.

     	Any ideas?  I'm sorry if this isn't more clear.  I'm rather new
     	to both X and C++.

     Jeff Rink
     (rink@tramp.colorado.EDU)

To the best of my knowledge, there's no way to make Xt directly call a
C++ member function.  If there is, I'd love to hear it.

The way to make Xt and C++ work together is to create a static C++ function
as the callback function for each callback.  This function receives events,
determines which C++ object should handle the event and then passes the
event to that C++ object.

Your callback function can determine which C++ object to call in at least
two ways.  One way is to, when you add the callback to the widget, set
the clientData (the second arg) to be a pointer to your C++ object.  Then,
when you get the callback, cast the clientData to a pointer to the
appropriate C++ type and call the appropriate event handling function on
the C++ object.

Another way which can be used for some toolkits (OSF/Motif at least) is to,
when creating the widget, set the userData resource on the widget to be a
pointer to the corresponding C++ object.  Then, when you get the callback,
get the userData resource from the given widget and cast the userData resource
value to the appropriate C++ type and call the appropriate event handling
function on that C++ object.

I've also heard of people using hash tables to map widgets to C++ objects,
but I'm not sure what advantage that has over the above two alternatives.

Bob Miner 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Object Design, Inc.                       ~      OOOOOO
1 New England Executive Park              ~   OOOO    OOOO
Burlington, MA 01803-5005  USA            ~  OOOOO    OOOOO
bob@odi.com  -or-  uunet!odi!bob          ~   OOOO    OOOO
voice: (617) 270-9797 FAX: (617) 270-3509 ~      OOOOOO    bject Design Inc.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   "From there to here, from here to there, funny things are everywhere."
								- Dr. Seuss
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

nazgul@alphalpha.com (Kee Hinckley) (03/10/91)

In article <1991Mar9.123358.14352@csn.org> rink@tramp.colorado.edu (Jeff) writes:
>
>
>Howdy.
>Help requested from C++ and X gurus:
>
>
>	I am trying to set up a callback for an X widget.  I would LIKE
>	to have the callback be a member-function of a C++ class.
>	An example:

The only way you can make this work is to declare the member function
static.  Then it's up to you to figure out how to pass "this".  The
easiest way is in the the callback data, the second easiest is in
the widgets userData resource.  It would be nice if C++ allowed you
to then assign this and proceed normally, but....

If you want to do virtual functions, declare two callbacks, one static
and one virtual.  All the static one does is call the virtual one.

					-kee
-- 
Alfalfa Software, Inc.          |       Poste:  The EMail for Unix
nazgul@alfalfa.com              |       Send Anything... Anywhere
617/646-7703 (voice/fax)        |       info@alfalfa.com

I'm not sure which upsets me more: that people are so unwilling to accept
responsibility for their own actions, or that they are so eager to regulate
everyone else's.

kittu@shakti.wpd.sgi.com (Krishna Swaroop Kolluri) (03/10/91)

What you can do is to declare the callback as a friend function of the
class. This fried function should take a pointer to the object of the
class as argument which you can pass as client_data. 

Hope that helps!
kittu..
--
-------
Krishna Swaroop Kolluri		9U-530		kittu@sgi.com
Silicon Graphics, Inc.				Off:(415)335-1859
						Fax:(415)969-2314

XITIJSCH@DDATHD21.BITNET (03/11/91)

Try the following (I assume you use an ANSI C preprocessor):


--- This goes into a general header file:

#define use_as_callback(Class, foo) \
        void foo##_dispatch(Widget w, Class *object, caddr_t call_data) \
        { \
                object->foo(w, 0, call_data); \
        }

#define Cpp_callback(foo) foo##_dispatch



--- This is your C++ code:

class Name_of_class {
        ...
        void do_it(Widget w, caddr_t client_data, caddr_t call_data);
        ...
};

use_as_callback(Name_of_class, do_it);

..

        Name_of_class object;
        ...
        XtAddCallback(w, Cpp_callback(do_it), &object);

or, if you use Motif, you will register Cpp_callback(do_it) and pass &object
via an UIL identifier, etc.

There remains a problem with this approach: You cannot pass client_data
-- but perhaps you may attach it as userdata to the widget.

--
Joachim Schrod                          <xitijsch@ddathd21.bitnet>
Technical University of Darmstadt

baecker@gmdzi.gmd.de (Andreas Baecker) (03/13/91)

We have made the approach described below. It allows all kinds of functions,
including member functions, to be used as callbacks. It also provides a means
for using virtual functions as callbacks.

Consider the following definitions:

class CallbackTest {
  public:
    CallbackTest(Gcomposite *parent);

  protected:

      // These functions should be called as callbacks
      // All callbacks MUST have their last argument of type caddr_t

      // This is one which doen't have a client_data

    void CallbackWithoutClientData(caddr_t call_data);

      // This one has one client_data argument

    void CallbackWithOneClientData(void *client_data, caddr_t call_data);

      // This one has two client_data arguments

    void CallbackWithTwoClientData(void *client_data_1, void *client_data_2,
				   caddr_t call_data);
    
      // This one is a "wrapper" for a virtual function with one client_data

    void VirtualWrapper(void *client_data, caddr_t call_data);

      // This is the virtual function which is wrapped by the above function

    virtual void Wrapped(void *client_data, caddr_t call_data);

  private:

      // This is a C++ PushButton object which has the above functions in it's
      // activateCallback list.

    GpushButton button;
}

And here is how we attach the member functions as callbacks to the button widget.
We do this in the constructor:

CallbackTest::CallbackTest(Gcomposite *parent)
{
    button.create(parent, "someButton", /* managed = */ True );

    // Create a callback object for a callback without client_data
    // Save a pointer to the callback object on a local variable

    Gcallback * cb_1 = CALLBACK(CallbackTest, CallbackWithoutClientData, this);

    button.add_activate_callback(cb_1);

    // Create a callback object with one client_data (the button's address)
    // This one uses an anonymous callback object

    button.add_activate_callback(CALLBACK1(CallbackTest,
					   CallbackWithOneClientData, this,
					   GpushButton *, &button));

    // Create a callback object with two client_data (button & cb_1)

    button.add_activate_callback(CALLBACK2(CallbackTest,
					   CallbackWithTwoClientData, this,
					   GpushButton *, &button,
					   Gcallback *, cb_1));

    // This one attaches the virtual wrapper as a callback

    button.add_activate_callback(CALLBACK1(CallbackTest, VirtualWrapper, this
					   GpushButton *, &button));
}

CallbackTest::VirtualWrapper(void *client_data, caddr_t call_data)
{
    Wrapped(client_data, call_data);
}

The idea is to introduce so-called callback objects (class Gcallback).
Complete listings of Gcallback.h and Gcallback.C are appended to this message.

In the case of callbacks without client_data, these objects contais a pointer
to the function to be called and a "this" pointer.

Classes derived from Gcallback have additional members which hold the
client_data arguments.  In our current implementation, we have two derived
classes GcallbackOne and GcallbackTwo which hold one or two client_data
arguments, respectively. 

Callback objects may be used as ordinary callbacks, as event handlers, 
as protocol callback handlers and as timeout handlers (XtAppAddTimout).

The macros CALLBACK, CALLBACK1 and CALLBACK2 create "new" callback objects
of classes Gcallback, GcallbackOne and GcallbackTwo, respectively.
These macros provide all necessary type checking and therefor increase
security and reduce the amount of code to be written. Here is the definition
of CALLBACK1:

#define CALLBACK1(class, function, object, client_data_class_one, client_data_1)\
         (new GcallbackOne((GinaClassCallbackProcOne)(class :: function), \
			   (void *)(class *)(object), \
			   (void *)(client_data_class_one)(client_data_1)))

The macro guarantees that "function" is a member function of class "class", and
that "object" could be casted to a pointer to an instance of "class".

The class Gcallback has a general static callback handler which is an
XtCallbackProc:

static Gcallback::widget_handler(Widget w, client_data cl, caddr_t call_data)
{
    Gcallback *cb = (Gcallback *)client_data;

    cb->evaluate(call_data);
}

The callback handler is the function which is really added to a widget's
callback list when add_activate_callback() is called. The callback handler gets
the callback object as it's client_data and a widget-dependent call_data.

Note that evaluate() is a public function. It is therefor possible to call
it from everywhere in a program. This provides a means for implementing
callback in other contexts, which have nothing to do with widget. It is also
possible to implement additional callback lists in classes derived from
the classes which wrap the Motif widget classes, especially in any kind of
dialogs. We can provide an example if it is desired.

The callback handler calls the virtual function Gcallback::evaluate(). 
Gcallback::evaluate() then calls the C++ function with it's arguments.
We show the implementation of Gcallback::evaluate() and GcallbackOne::evaluate():

// We include this-> for documentation.

void Gcallback::evaluate(caddr_t call_data)
{
    (this->function_name)(this->object, this->call_data);
}

void GcallbackOne::evaluate(caddr_t call_data)
{
    (this->function_name)(this->object, this->client_data_one, this->call_data);
}

The PushButton's member function add_activate_callback() calls 
Gcallback::attach_to_widget() and provides all necessary arguments for attaching
a callback:

void GpushButton::add_activate_callback(Gcallback *cb)
{
    cb->attach_to_widget(this->get_widget_id(), XmNactivateCallback);
}

Finally, Gcallback::attach_to_widget() calls XtAddCallback to add the callback:

void Gcallback:: attach_to_widget(Widget widget, char *name)
{
    Require( cb_type == CB_NOT_ATTACHED );

    cb_type = CB_WIDGET;
    cb_info.callback.widget = widget;
    cb_info.callback.name = name;
    XtAddCallback(widget, name,
		  (XtCallbackProc)Gcallback::widget_handler,
		  (XtPointer)this);
}

Using non-member functions as callbacks is similar. You may read through the
source code and see how it is done.

Andreas

==============================================================================
Mail:  Andreas Baecker
       GMD Gesellschaft fuer Mathemathik und Datenverarbeitung mbH
       (The German National Research Center for Computer Science)
       Schloss Birlinghoven
       D-5205 Sankt Augustin 1
       Germany

email: baecker@gmdzi.gmd.de
PHONE: +49-2241-142078
FAX  : +49-2241-142618
==============================================================================


Listing of Gcallback.h
======================

/* @(#)Gcallback.h	1.14 2/25/91 */

#ifndef Gina_Callback_H
#define Gina_Callback_H

#include <Gina/Gobject.h>

#ifdef USE_NIHCL
#  define Object XObject
#  define String XString
#endif USE_NIHCL

#include <Xm/AtomMgr.h>

#ifdef USE_NIHCL
#  undef Object
#  undef String
#endif USE_NIHCL

class Gtimer;

typedef void (*GinaSimpleCallbackProc) (caddr_t);

class Gcallback : public Gobject {
    friend class Gtimer;
  public:
    typedef enum GinaCallbackType { CB_NOT_ATTACHED, CB_WIDGET, CB_EVENT,
				    CB_PROTOCOL, CB_TIMER};
  public:
    Gcallback(GinaSimpleCallbackProc);
    ~Gcallback();
    
    virtual void evaluate(caddr_t call_data);
    void attach_to_widget(Widget, char *);
    void attach_to_widget(Widget, EventMask mask, Boolean non_maskable);
    void attach_to_widget(Widget, Atom, Atom);
    void attach_to_timer(Gtimer *);
    void remove();
    
  protected:

    // Callback handlers (called from "C" only)
    static void widget_handler(Widget w, Gcallback* client_data,
			       caddr_t call_data);
    static void timer_handler(Gtimer *, XtIntervalId *);
    
    GinaSimpleCallbackProc function_name;
    GinaCallbackType cb_type;
    union {
	struct {Widget widget; char *name; } callback;
	struct {Widget widget; EventMask mask; Boolean non_maskable;} event;
	struct {Widget widget; Atom property, protocol; } atom;
	struct {Gtimer *timer; } timer;
    } cb_info;
};

DefGenericList(GcallbackList, Gcallback);

typedef void (*GinaClassCallbackProc) (void *, caddr_t);

class GclassCallback : public Gcallback {
  public:
    GclassCallback(GinaClassCallbackProc proc, void* object);
    virtual void evaluate(caddr_t call_data);
  protected:
    void *object;
};

typedef void (*GinaClassCallbackProcOne) (void *, void *, caddr_t);

class GcallbackOne : public GclassCallback {
  public:
    GcallbackOne(GinaClassCallbackProcOne proc, void* object, void *);
    virtual void evaluate(caddr_t call_data);
  protected:
    void *client_data_one;
};

typedef void (*GinaClassCallbackProcTwo) (void *, void *, void *, caddr_t);

class GcallbackTwo : public GcallbackOne {
  public:
    GcallbackTwo(GinaClassCallbackProcTwo proc, void*, void *, void *);
    virtual void evaluate(caddr_t call_data);
  protected:
    void *client_data_two;
};

#define SIMPLE_CALLBACK(function)  (new Gcallback((function)))

#define SIMPLE_CALLBACK1(function, client_data_class, client_data) \
           (new GclassCallback((function), \
		               (void *)(client_data_class)(client_data)))

#define SIMPLE_CALLBACK2(function, client_data_class_one, client_data_one, client_data_class_two, client_data_two) \
          (new GcallbackOne((function), \
                            (void *)(client_data_class_one)(client_data_one),\
			    (void *)(client_data_class_two)(client_data_two)))

#define CALLBACK(class, function, object) \
         (new GclassCallback((GinaClassCallbackProc)(class :: function), \
			     (void *)(class *)(object)))

#define CALLBACK1(class, function, object, client_data_class_one, client_data_1) \
         (new GcallbackOne((GinaClassCallbackProcOne)(class :: function), \
			   (void *)(class *)(object), \
			   (void *)(client_data_class_one)(client_data_1)))

#define CALLBACK2(class, function, object, client_data_class_one, client_data_1, client_data_class_two, client_data_2) \
         (new GcallbackTwo((GinaClassCallbackProcTwo)(class :: function), \
                           (void *)(class *)(object), \
			   (void *)(client_data_class_one)(client_data_1), \
			   (void *)(client_data_class_two)(client_data_2)))

#endif Gina_Callback_H

================================================================================

Listing of Gcallback.C:
=======================

/* @(#)Gcallback.C	1.10 2/25/91 */

#include <Gina/Gobject.h>
#include <Gina/Gcallback.h>
#include <Gina/Gtimer.h>

extern "C" {
extern void XmAddProtocolCallback(Widget, Atom, Atom, XtCallbackProc,
				  XtPointer);
extern void XmRemoveProtocolCallback(Widget, Atom, Atom, XtCallbackProc,
				     XtPointer);
}

Gcallback::
Gcallback(GinaSimpleCallbackProc proc)
{
    function_name = proc;
    cb_type = CB_NOT_ATTACHED;
}

Gcallback::
~Gcallback()
{
    if( cb_type != CB_NOT_ATTACHED )
	remove();
}

void Gcallback::
evaluate(caddr_t call_data)
{
    (function_name)(call_data);
}

void Gcallback::
attach_to_widget(Widget widget, char *name)
{
    Require( cb_type == CB_NOT_ATTACHED );

    cb_type = CB_WIDGET;
    cb_info.callback.widget = widget;
    cb_info.callback.name = name;
    XtAddCallback(widget, name,
		  (XtCallbackProc)Gcallback::widget_handler,
		  (XtPointer)this);
}

void Gcallback::
attach_to_widget(Widget widget, EventMask mask, Boolean non_maskable)
{
    Require( cb_type == CB_NOT_ATTACHED );

    cb_type = CB_EVENT;
    cb_info.atom.widget = widget;
    cb_info.event.mask = mask;
    cb_info.event.non_maskable = non_maskable;
    XtAddEventHandler(widget, mask, non_maskable,
		      (XtEventHandler)Gcallback::widget_handler,
		      (XtPointer)this);
}

void Gcallback::
attach_to_widget(Widget widget, Atom property, Atom protocol)
{
    Require( cb_type == CB_NOT_ATTACHED );

    cb_type = CB_PROTOCOL;
    cb_info.atom.widget = widget;
    cb_info.atom.property = property;
    cb_info.atom.protocol = protocol;
    XmAddProtocolCallback(widget, property, protocol,
			  (XtCallbackProc)Gcallback::widget_handler,
			  (XtPointer)this);
}

void Gcallback::
attach_to_timer(Gtimer *timer)
{
    Require( cb_type == CB_NOT_ATTACHED );

    cb_type = CB_TIMER;
    cb_info.timer.timer = timer;
}

void Gcallback::
remove()
{
    Require( cb_type != CB_NOT_ATTACHED );

    switch(cb_type) {
      case CB_WIDGET:
	XtRemoveCallback(cb_info.callback.widget, cb_info.callback.name,
			 (XtCallbackProc)function_name, (XtPointer)this);
	break;
      case CB_EVENT:
	XtRemoveEventHandler(cb_info.event.widget, cb_info.event.mask,
			     cb_info.event.non_maskable,
			     (XtEventHandler)function_name, (XtPointer)this);
	break;
      case CB_PROTOCOL:
	XmRemoveProtocolCallback(cb_info.atom.widget,
				 cb_info.atom.property,
				 cb_info.atom.protocol,
				 (XtCallbackProc)function_name,
				 (XtPointer)this);
	break;
      case CB_TIMER:
	cb_info.timer.timer->remove_callback(this);
	break;
      case CB_NOT_ATTACHED:
	break;
    }
    cb_type = CB_NOT_ATTACHED;
}

void Gcallback::
widget_handler(Widget, Gcallback *cb, caddr_t call_data)
{
    cb->evaluate(call_data);
}

void Gcallback::
timer_handler(Gtimer *timer, XtIntervalId *id)
{
    Require(*id == timer->id);
    Require( ! timer->destroyed );
    Require(timer->enabled);
    Require(timer->running);

    Gina_Debug_NL ("TimerDispatcher");

    timer->enabled = False; // TimeOut is removed automatically by Xt
    
    if( timer->active_in_handler )
	timer->install_timeout();
    
    timer->busy = True;
    for( timer->callbacks.start();
	! timer->callbacks.off(); timer->callbacks.forth() )
	timer->callbacks.item()->evaluate((caddr_t)(void *)timer);
    timer->busy = False;
    
    if(! timer->active_in_handler && timer->running &&
       ! timer->enabled && ! timer->destroyed )
	timer->install_timeout();
    
    if( timer->running  && timer->destroyed )
	timer->deinstall_timeout();
    
    if( timer->destroyed )
	delete timer;
}

GclassCallback :: GclassCallback (GinaClassCallbackProc proc, void* obj)
: ((GinaSimpleCallbackProc)proc)
{
    object = obj;
}

void GclassCallback::evaluate(caddr_t call_data)
{
    ((GinaClassCallbackProc)function_name)(object, call_data);
}

GcallbackOne::
GcallbackOne(GinaClassCallbackProcOne proc, void* obj, void *cd_one)
: ((GinaClassCallbackProc)proc, obj)
{
    client_data_one = cd_one;
}

void GcallbackOne::evaluate(caddr_t call_data)
{
    ((GinaClassCallbackProcOne)function_name)(object, client_data_one,
					      call_data);
}

GcallbackTwo::
GcallbackTwo(GinaClassCallbackProcTwo proc, void* obj, void *cd1, void *cd2)
: ((GinaClassCallbackProcOne)proc, obj, cd1)
{
    client_data_two = cd2;
}

void GcallbackTwo::evaluate(caddr_t call_data)
{
    ((GinaClassCallbackProcTwo)function_name)(object,
					      client_data_one,
					      client_data_two,
					      call_data);
}