[C++-sig] Marshalling Callbacks

David Abrahams dave at boost-consulting.com
Mon Dec 23 20:10:51 CET 2002


"William Trenker" <wtrenker at hotmail.com> writes:

> (Please excuse me if this was already posted.  

It wasn't, AFAICT.

> I'm having trouble
> using gmane.org's fabulous service; their auto-authorization mechanism
> appears to be dropping my confirmation messages.  I'm working with
> them on that.)

>>"William Trenker" <wtrenker at hotmail.com> writes:
>>
>> > 3-rd party GUI libraries, like FLTK, handle external events (such as
>> > mouse clicks) with a callback mechanism that is something like this
>> > very sketchy pseudo-code;
>> >
>> > -------------------3rd Party----------------------
>> > class Widget {     //low-level GUI object
>> > 	void callback(void* func, void* data) {
>>
>>         void callback(void (*func)(void*), void* data)
>>
>>??
>
> Yes, I should have mentioned that my cooked-up example was a hybrid
> based on the FLTK API but watered down by other API's I have known.
> FLTK's callback is, in fact, defined along the lines of your
> re-factoring.  Yet, there are many libraries out there that use void*
> all over the place, so I generalized.

Well, that's just plain non-portable.  You get unspecified or
undefined behavior (forget which) when you cast a function pointer to
an object pointer, even if you cast it back to call through it (they
might be different sizes, for example).

>>This is a somewhat regressive approach. More modern systems would
>>use overridable virtual functions of a common base class that can
>>package arbitrary data with the function, and avoids the type-safety
>>problems associated with casting to/from void*:
>>
>>     struct callback
>>     {
>>         virtual void operator()() = 0;
>>     };
>>
>
> Yes, again.  It sure is taking a long time for C++ to really take
> root in our profession.  (I never really started using C++ in it's
> own right until after I had worked for quite a while in Python and
> started thinking in classes.  As for templates?  I'm learning a lot
> in that department just by working with boost.)
>
>>Or much better still, boost::function, which has many other
>>advantages.
>
> (Hmm, I'd better take some time and look at all those other boost
> libraries I've downloaded.)

I sent Bill S. a short email about this possibility just now.

You should look at boost.function and especially boost.signals, then
look at Boost.Bind, and also Boost.Lambda if you have a very
conformant compiler.


>> > The python equivalent might be:
>> >
>> > import ext
>> > class A:
>> >    def __init__(self):
>> >       self.b = ext.Button()
>> >    def onClick(self,appInfo):
>> >       print "Mouse Clicked!"
>> >    def execute(self,info):
>> >       self.b.callback(self.onClick,info)
>> > 	  self.b.run()
>> > ---------------------------------------------------
>>
>>Most likely the Python equivalent would also use nullary function
>>objects, which you could use lambda expressions user-defined function
>>objects to bind data into... but anyway, I'll try to avoid redesigning
>>your interface... ;-)
>>
>
> And again, yes.  I'm familiar with members like this:
> class klass:
>     def overloadme(self,*args):
> 	pass

I was thinking more along the lines of:

   class Func:
       def __init__(self, x):
           self.data = x

       def __call__(self):
           # do something with self.data


> And I'm familiar with lambda's, but not in this application.  Can
> you give me a quick example?


  lambda x: x + 6

binds 6 into a function object much the way Func(y) binds y into a
function object. A nullary function object can be produced via:

   lambda: #some-expression.

So you might write:

def f(x):
   ...

z = #something
   
   ...

   widget.callback(lambda: f(z))

But a much more-common case of binding (also known as currying) would
be when you write:

   widget.callback(self.notify)

>>Fortunately, your callback interface has an additional void*
>>argument. So one thing you could do is to store a pointer to the
>>Python callable object in that void*.  Then you could write a thin
>>wrapper as follows:
>>
>>     // helper function
>>     void call_object(void* py_function)
>>     {
>>         PyObject* o = (PyObject*)py_function;
>>         boost::python::call<void>(o);
>>     }
>>
>>     // Wrap this one as a method of Widget; call it "callback":
>>     void callback_wrapper(Widget& w, boost::python::object f)
>>     {
>>         w.callback(call_object, f.ptr());
>>     }
>>
>
> Interesting, that's exactly what I did on my first BPL project
> (although it had only one callback to deal with).  Since any GUI,
> including FLTK, makes its living firing off callbacks maybe I should
> make a callback factory that uses one of the boost container
> libraries and boost::pool as a dynamic store.  Or I could use a BPL
> 'object' and employ embedded python for storage of these callback
> objects.  Does that make any sense?

Sort of, though it's a little unclear to me *precisely* what you mean.
But either way you're still going to run up against the lifetime
management issues mentioned below.

> If I am understand correctly, your point is that when software
> components interact with each other's dynamically created objects
> those components should be handing out references to their objects
> using a mechanism that accounts for object life.

Or at least, a mechanism that /can/ account for object life.  In C++
that means allowing the use of non-dynamic objects which encapsulate
the reference to the dynamic objects (shared_ptr<T>, auto_ptr<T>,
boost::function<...>...).  You can do that by writing templates that
pass arbitrary data objects around, or you can do it by passing a
fixed type of data object that uses some form of runtime-polymorphic
dispatching.

> As for FLTK, like many others, there appears to be a simplifying
> assumption that if a clickable object is on the screen then the
> callback for that object must still be viable.  I guess the use of
> smart pointers only works if both ends of the pointer, so to speak,
> are smart-pointer aware.

Not neccessarily.  Again, I'm a little fuzzy on what you mean, but
boost::shared_ptr does some pretty magical things.  For one thing,
it's non-intrusive, so the pointee doesn't need to be "aware".  The
code handling the shared_pointer does, of course.

>>So it looks like you have to choose the least odious alternative of
>>all of these.
>>
>>FWIW, this problem has basically nothing to do with Boost.Python; it's
>>a problem of the FLTK interface.
>
> And the FLTK interface is one of the better ones I've worked with.
> I'm not complaining about BPL, or FLTK.  I'm just making sure I
> haven't overlooked something fundamental before I launch into the
> grunt work of hand-crafting wrappers for all of the many, member-laden
> classes that make up FLTK.
>
> Thanks again, David, for steering me on the right BPL course.

My pleasure.  Good luck.
BTW, how do you plan to handle the lifetime issue?

-- 
                       David Abrahams
   dave at boost-consulting.com * http://www.boost-consulting.com
Boost support, enhancements, training, and commercial distribution





More information about the Cplusplus-sig mailing list