class method using callback decorator

419 views
Skip to first unread message

Xiaoqiang Wang

unread,
May 21, 2014, 8:08:06 AM5/21/14
to pytho...@googlegroups.com

Hi,

I tried to use @ffi.callback decorator to a class method, with no success.
ffi.callback works as a function though.


from cffi import FFI

ffi = FFI()

ffi.cdef("""
    typedef void CALLBACK(int);
    void add_callback(CALLBACK func);
""")

lib = ffi.verify("""
    typedef void CALLBACK(int);
    void add_callback(CALLBACK func) {
        func(12);
    }
""")


class Foo(object):

    @ffi.callback('void(int)')
    def callback_function(arg):
        print arg

    def callback_method(self, arg):
        print arg

    @ffi.callback('void(int)')
    def callback_method_decorated(self, arg):
        print arg

foo = Foo()
# plain function works
lib.add_callback(foo.callback_function)

# plain bound method works
pfunc = ffi.callback('void(int)', foo.callback_method)
lib.add_callback(pfunc)

# decorated bound method does not
lib.add_callback(foo.callback_method_decorated)

Armin Rigo

unread,
Jun 29, 2014, 4:50:31 AM6/29/14
to pytho...@googlegroups.com
Hi,

On 21 May 2014 14:08, Xiaoqiang Wang <xiaoqi...@gmail.com> wrote:
> class Foo(object):
> (...)
> @ffi.callback('void(int)')
> def callback_method_decorated(self, arg):
> print arg
> (...)
> lib.add_callback(foo.callback_method_decorated)

It is hard to do anything more explicit. From cffi's point of view,
this is the same as:

class Foo(object):
p = ffi.new("char[]", 10)
print Foo().p

I.e. cdata pointer objects don't have a __get__, so you can read them
unmodified out of instances or classes. To do anything better in this
case would require adding a __get__ that returns a copy of the
callback cdata object in which the "self" argument is stored. Such a
__get__ would have to detect the case where it is a function pointer
implemented as a callback, and not do anything in other cases (e.g. a
normal function pointer, or any other kind of cdata). It's not
impossible, but a bit of work...

More importantly, it would be very slow: it would need a new libffi
callback each time you read the attribute
"foo.callback_method_decorated". Better to try to make callbacks be
just plain global functions, which is documented and follows more
closely the C way. Any general-purpose C callback should take a
generic "void *" argument, which is convenient to pass a
ffi.new_handle().


A bientôt,

Armin.

Xiaoqiang Wang

unread,
Jun 30, 2014, 3:07:03 AM6/30/14
to pytho...@googlegroups.com, ar...@tunes.org
Hi,

I saw once that there is plan to remove the function call usage of ffi.callback
That worries me when I see it cannot work with class method.

I beg not to remove the possibility to use it as a function,  before @callback decorator works with class method, 

Best
Xiaoqiang

Armin Rigo

unread,
Jun 30, 2014, 3:25:21 AM6/30/14
to pytho...@googlegroups.com
Hi Xiaoqiang,

On 30 June 2014 09:07, Xiaoqiang Wang <xiaoqi...@gmail.com> wrote:
> I beg not to remove the possibility to use it as a function, before
> @callback decorator works with class method,

As I tried to explain, the fastest (and most C-ish) way to use it with
a class method is to have a global function anyway:

@ffi.callback("int(int, void *)")
def my_global_callback(x, handle):
return ffi.from_handle(handle).some_method(x)

This is used like this:

lib.c_function_with_a_callback_and_voidp_arg(
my_global_callback, ffi.new_handle(self))


A bientôt,

Armin.

Xiaoqiang Wang

unread,
Jun 30, 2014, 3:54:18 AM6/30/14
to pytho...@googlegroups.com, ar...@tunes.org

Hi Armin,

Thanks for the further example. This usage assumes the callback function signature has a void pointer argument.
While sometimes this isn't case for some libraries, which is not upon me to decide.

In such a case, if it is not possible to pass bounded class method using the function form of ffi.callback,
I would resort to global variables which involves housekeeping work, when multiple callbacks need to be added/removed.

I hope I did not make the argument in a absurd way. That is the problem I encountered.

Best
Xiaoqiang

Armin Rigo

unread,
Jul 1, 2014, 12:09:45 PM7/1/14
to pytho...@googlegroups.com
Hi,

On 30 June 2014 09:54, Xiaoqiang Wang <xiaoqi...@gmail.com> wrote:
> Thanks for the further example. This usage assumes the callback function
> signature has a void pointer argument.
> While sometimes this isn't case for some libraries, which is not upon me to
> decide.

You can only complain to the designers of the library. If a callback
doesn't take a generic "void *" argument, then you have the same
problem even more acutely in C: there is no way to attach "more than
one callback". Then you need to work around. CFFI doesn't offer a
clean solution for that either, although in this case (and provided
the number of instances is still reasonable) I would recommend this:

class A:
def __init__(self):

def callback

Armin Rigo

unread,
Jul 1, 2014, 12:11:24 PM7/1/14
to pytho...@googlegroups.com
Re-hi,

...sorry, wrong manipulation.

class A:
def __init__(self):
@ffi.callback("int(int)")
def cb(x):
return x + self.y
self.my_cb = cb


A bientôt,

Armin.

Xiaoqiang Wang

unread,
Jul 1, 2014, 12:20:37 PM7/1/14
to pytho...@googlegroups.com, ar...@tunes.org
Thanks for the demo. I haven't thought of using inner functions.

Best
Xiaoqiang

Mark Santcroos

unread,
Dec 8, 2015, 10:28:50 AM12/8/15
to python-cffi, ar...@tunes.org
Hi Armin,


On Monday, June 30, 2014 at 3:25:21 AM UTC-4, Armin Rigo wrote:
As I tried to explain, the fastest (and most C-ish) way to use it with
a class method is to have a global function anyway:

@ffi.callback("int(int, void *)")
def my_global_callback(x, handle):
    return ffi.from_handle(handle).some_method(x)

This is used like this:

    lib.c_function_with_a_callback_and_voidp_arg(
        my_global_callback, ffi.new_handle(self))

This particular construct was really helpful to me and you might consider extending the documentation about callbacks with this particular snippet.

Thanks!

Mark

Armin Rigo

unread,
Dec 9, 2015, 2:45:59 AM12/9/15
to Mark Santcroos, python-cffi
Hi Mark,

On Tue, Dec 8, 2015 at 4:28 PM, Mark Santcroos
<mas...@scarletmail.rutgers.edu> wrote:
>> This is used like this:
>>
>> lib.c_function_with_a_callback_and_voidp_arg(
>> my_global_callback, ffi.new_handle(self))
>
> This particular construct was really helpful to me and you might consider
> extending the documentation about callbacks with this particular snippet.

Good idea! Added in 9d10c2d8a77b. Also, note that my example is
bogus because `ffi.new_handle(x)` must be kept alive:

class Foo(object):
def __init__(self):
handle = ffi.new_handle(self)
self._handle = handle # must be kept alive
lib.register_stuff_with_callback_and_voidp_arg(my_global_callback,
handle)
def some_method(self, x):
...

Sorry for posting a wrong example!


A bientôt,

Armin.
Reply all
Reply to author
Forward
0 new messages