Path: utzoo!news-server.csri.toronto.edu!cs.utexas.edu!uunet!math.fu-berlin.de!opal!unido!gmdzi!baecker From: baecker@gmdzi.gmd.de (Andreas Baecker) Newsgroups: comp.windows.x Subject: Re: C++ and X: Member-Functions as Callbacks Message-ID: <4273@gmdzi.gmd.de> Date: 13 Mar 91 10:07:19 GMT References: <1991Mar9.123358.14352@csn.org> Organization: GMD, St. Augustin, F.R. Germany Lines: 504 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 #ifdef USE_NIHCL # define Object XObject # define String XString #endif USE_NIHCL #include #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 #include #include 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); }