ffi.cdef(""" struct ev_io { ...; } struct my_io { ev_io io; void *handle; ...; }; """) ffi.set_source("_example", """ static int (*python_callback)(void *handle, int revents); static void (*python_handle_error)(void *handle); static void my_io_callback(struct ev_loop *loop, ev_io *io, int revents) { /* invoke 'self.callback()' */ void *handle = ((struct my_io *)io)->handle; if (python_callback(handle, revents) < 0) { /* in case of exception, call 'self.loop.handle_error()' */ python_handle_error(handle); } ... more code to stop the event, like _run_callback() does ... } """) # this is done globally: @ffi.callback("int(void *handle, int revents)", error=-1) def python_callback(handle, revents): watcher = ffi.from_handle(handle) watcher.callback(*watcher.args) return 0 lib.python_callback = python_callback # then this is done in class io's __init__: class io(watcher): def __init__(self, ...): self._handle = ffi.new_handle(self) self._watcher = ffi.new("my_io *") self._watcher.handle = self._handle # give the same C-level callback function to all of them self._watcher_init(self._watcher, lib.my_io_callback)