[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