From: soloway@midniteoil.Eng.Sun.COM (Mark Soloway)
Newsgroups: comp.lang.c++
Subject: Re: Callbacks in C++
Date: 8 Jan 1993 22:03:03 GMT
Organization: Sun Microsystems, Inc.
Distribution: world
Reply-To: soloway@midniteoil.Eng.Sun.COM
NNTP-Posting-Host: midniteoil

In article 1iikcuINN6fr@news.cerf.net, hlf@nic.cerf.net (Howard Ferguson) writes:
>The FAQ for this group describes how to put wrapper functions around 
>member functions so they can be called as callbacks from X libraries
>and the like. But if I am writting a c++ class library, surley I should
>be able to provide a cleaner mechanism to my customers. Is there
>a standard way of doing this???

This question comes up often enough that it should be added to the FAQ...

Anyway, I sent out the article that follows several months ago.  It describes
a method I used for creating C++ wrappers around XView packages.  It can be
generalized to other toolkits fairly easily...

							- Mark
--------------------------------------------------------------------
This article describes a method I use to provide a convenient C++
callback interface for XView and provide the ability to encapsulate
callback behavior in a subclass.  The method described is used in
UIT V2.

In order to provide a convenient C++ callback interface for XView
you want the callback to call a member function of the class that
is encapsulating the XView package.  Ordinary (non-static) member
functions provide an implicit "this" pointer for access to other members
of the object.  Many compilers pass the "this" pointer as an invisible
first argument in the member function.  This means that the following
member function:

	ClassName::memberFunction (Panel_item item, Event *event)

can look like the following in the C code generated by cfront:

	memberFunction__9ClassNameF10Panel_itemP5Event (ClassName  *this,
			   		                Panel_item  item,
					                Event      *event)

	(See C++ Annotated Refernce Manual (ARM), section 7.2 for
	 more information regarding funtion name encoding).

This causes a problem with callbacks becuase of the "this" argument.
The way to get around this is by using a static member function in
callbacks.  Static member functions do not provide a "this" pointer
to access ordinary class members and do not have a "this" argument in
the C code form of the member function.  If you have the following static
member function defined for a Button class (encapsulating the XView
PANEL_BUTTON package):

	void Button::notifyProc (Panel_item item, Event *event)

You can specify it as a callback in the following way:

	// Create the XView button and store it in a data mwember
	// of the Button class (Button::xviewButton).
	xviewButton = xv_create (owner, PANEL_BUTTON,
			         PANEL_NOTIFY_PROC, &Button::notifyProc,
				 NULL);

With some compilers (g++, for example) the static member function must
be public if you want to get the address of it.  This is not true for
most AT&T derived compilers.

In order to actually provide a convenient callback interface you really
need to get hold of a pointer to the Button object when the callback is
called.  This can be done by attaching data to the XView object that
calls the callback.  XV_KEY_DATA provides an easy way to do this.  First
a constant key value must be created.  Here are 2 ways of doing this:

	1) Use a "const int" or a "#define" to define a key value in
	   a header file that will be used for this purpose:

		const int THIS_POINTER_KEY = 100;

	   or:

		#define THIS_POINTER_KEY 100

	   Of course, you must make sure the key value you choose is unique
	   for your application.  Or,

	2) Create a global variable and use xv_unique_key to initialize
	   the value:

		int this_pointer_key_g;

		void main (int argc, char **argv)
		{
		  this_pointer_key_g = xv_unique_key();

                  ...
		}

After creating the unique key, use the key to store the encapsulating object
"this" pointer in the XView button object:

		xv_set (xviewButton,
			XV_KEY_DATA, THIS_POINTER_KEY, this,
			NULL);

A basic notifyProc member function that calls a user specified callback
handler might look like this:

	void Button::notifyProc (Panel_item item, Event *event)
	{
	  Button *object = (Button *)xv_get(item,
	                                    XV_KEY_DATA,
					    THIS_POINTER_KEY);

	  // If a notify handler has been specified, via
	  // "void Button::setNotifyHandler (void (*)(Button *, Event *))",
	  // then call it.
          if (object->notifyHandler)
            (*object->notifyHandler)(object, event);
	}

The Button class might look like this:

        class Button {
        public:
          ... 
	  void setNotifyHandler(void (*handler)(Button *, Event *))
	    {
	      notifyHandler = handler;
	    }
 
        protected:
          // Make xviewButton protected so that derived classes can get hold
          // of the XView button object when inside of their personalized
          // handlers.
          Panel_item xviewButton;
 
        private:
          static void   notifyProc(Panel_item, Event *);
          void        (*notifyHandler)(Button *, Event *);
        }

This provides a "convenient" callback interface, but it does not provide
for encapsulation of callback behavior in the class.  To provide the ability
to encapsulate the behavior, you need to provde a virtual function that
will be overridden in subclasses that are encapsulating the callback
behavior:

	class Button {
	public:
	  ...

	protected:
	  // Make xviewButton protected so that derived classes can get hold
	  // of the XView button object when inside of their personalized
	  // handlers.
          Panel_item xviewButton;

	private:
	  static  void notifyProc(Panel_item, Event *);
	  virtual void notifyHandler(Event *);
	}

	void Button::notifyProc (Panel_item item, Event *event)
	{
	  Button *object = (Button *)xv_get(item,
	                                    XV_KEY_DATA,
					    THIS_POINTER_KEY);

	  object->notifyHandler(event);
	}
	
You can then define the behavior of notifyHandler in a class derived
from Button:

	class FileButton : public Button {
	public:
	  ...

	private:
	  virtual notifyHandler(Button *, Event *);
	}

	void FileButton::notifyHandler (Event *event)
	{
	  // Do whatever I want...
	}

Because of virtual functions, FileButton::notifyHandler will have
an implicit "this" pointer to the FileButton object that is encapsulating
the XView object the callback occurred on.

					- Mark
__________________________________________
\_Mark Soloway (mark.soloway@Eng.Sun.COM)_\
/_Distributed Systems Services (ToolTalk)_/
\__SunSoft (A Sun Microsystems Company)___\



Newsgroups: comp.lang.c++