Author: Armin Rigo <ar...@tunes.org> Branch: static-callback Changeset: r2401:ff2c67fc1655 Date: 2015-11-15 11:07 +0100 http://bitbucket.org/cffi/cffi/changeset/ff2c67fc1655/
Log: Write documentation about ffi.call_python(). diff --git a/doc/source/using.rst b/doc/source/using.rst --- a/doc/source/using.rst +++ b/doc/source/using.rst @@ -422,8 +422,195 @@ with ``int foo();`` really means ``int foo(void);``.) -Callbacks ---------- +.. _`call_python`: + +Calling Python from C (new style) +--------------------------------- + +When the C code needs a pointer to a function which invokes back a +Python function of your choice, here is how you do it in the +out-of-line API mode. The next section about Callbacks_ describes the +ABI-mode solution. + +This is *new in version 1.4.* Use Callbacks_ if backward compatibility +is an issue. + +In the builder script, declare in the cdef a function prefixed with +``CFFI_CALL_PYTHON``:: + + ffi.cdef(""" + typedef ... footype_t; + CFFI_CALL_PYTHON int my_callback(footype_t *, int); + + void library_function(int(*callback)(footype_t *, int)); + """) + ffi.set_source("_my_example", """ + #include <some_library.h> + """) + +The function ``my_callback()`` is not expected to be found in the C +code you pass with ``set_source()``. Instead, it is defined by CFFI +to call back some Python code. The Python code that is called is +attached at run-time, i.e. in the application's code:: + + from _my_example import ffi, lib + + @ffi.call_python() + def my_callback(fooptr, value): + return 42 + +Then any call to the C function ``my_callback`` will invoke the +corresponding Python function. From Python you can get a ``<cdata>`` +pointer-to-function object from either ``lib.my_callback`` or directly +from the decorated ``my_callback`` above. + +Note that there is only one C function per ``CFFI_CALL_PYTHON`` line +in the cdef. You can redefine the attached Python function by calling +``@ffi.call_python()`` again, but it changes the C logic to call the +new Python function; the C function pointer is always the same. This +is not suitable if you need a variable number of C function pointers. + +However, this result is not possible either in pure C code. For this +reason, it is usual for C to define callbacks with a ``void *data`` +argument. You can use ``ffi.new_handle()`` and ``ffi.from_handle()`` +to pass a Python object through this ``void *`` argument. For +example, if the C type of the callbacks is:: + + typedef void (*event_cb_t)(struct event *evt, void *userdata); + +and you register events by calling this function:: + + void event_cb_register(event_cb_t cb, void *userdata); + +Then you would write this in the build script:: + + ffi.cdef(""" + typedef void (*event_cb_t)(struct event *evt, void *userdata); + void event_cb_register(event_cb_t cb, void *userdata); + + CFFI_CALL_PYTHON void my_event_callback(struct event *, void *); + """) + ffi.set_source("_demo_cffi", """ + #include <the_event_library.h> + """) + +and in your main application you register events like this:: + + from _demo_cffi import ffi, lib + + class Widget(object): + def __init__(self): + userdata = ffi.new_handle(self) + self._userdata = userdata # must keep this alive! + lib.event_cb_register(my_event_callback, userdata) + + def process_event(self, evt): + ... + + @ffi.call_python() + def my_event_callback(evt, userdata): + widget = ffi.from_handle(userdata) + widget.process_event(evt) + +Some other libraries don't have an explicit ``void *`` argument, but +let you attach the ``void *`` to an existing structure. For example, +the library might say that ``widget->userdata`` is a generic field +reserved for the application. If the event's signature is now this:: + + typedef void (*event_cb_t)(struct widget *w, struct event *evt); + +Then you can use the ``void *`` field in the low-level +``struct widget *`` like this:: + + from _demo_cffi import ffi, lib + + class Widget(object): + def __init__(self): + ll_widget = lib.new_widget(500, 500) + self.ll_widget = ll_widget # <cdata 'struct widget *'> + userdata = ffi.new_handle(self) + ll_widget.userdata = userdata # this makes a copy of the "void *" + self._userdata = userdata # must *still* keep this alive! + lib.event_cb_register(ll_widget, my_event_callback) + + def process_event(self, evt): + ... + + @ffi.call_python() + def my_event_callback(ll_widget, evt): + widget = ffi.from_handle(ll_widget.userdata) + widget.process_event(evt) + +In case you want to access ``my_event_callback`` directly from the C +code written in ``set_source()``, you need to write a forward static +declaration (the real implementation of this function is added by CFFI +after this C code):: + + ffi.set_source("_demo_cffi", """ + #include <the_event_library.h> + + static void my_event_callback(struct widget *, struct evt *); + + /* more C code which uses '&my_event_callback' or even + directly calls my_event_callback() */ + """) + +Note that ``CFFI_CALL_PYTHON`` cannot be a variadic function type +for now (this may be implemented in the future). + +Be careful when writing the Python callback function: if it returns an +object of the wrong type, or more generally raises an exception, then +the exception cannot be propagated. Instead, it is printed to stderr +and the C-level callback is made to return a default value. This can +be controlled with ``error`` and ``onerror``, described next. + +The ``@ffi.call_python()`` decorator takes these optional arguments: + +* ``name``: the name of the ``CFFI_CALL_PYTHON`` function declaration + from the cdef. Defaults to the name of the Python function you + decorate. + +.. _error_onerror: + +* ``error``: the returned value in case the Python function raises an + exception. It is 0 or null by default. The exception is still + printed to stderr, so this should be used only as a last-resort + solution. + +* ``onerror``: if you want to be sure to catch all exceptions, use + ``@ffi.call_python(onerror=func)``. If an exception occurs and + ``onerror`` is specified, then ``onerror(exception, exc_value, + traceback)`` is called. This is useful in some situations where you + cannot simply write ``try: except:`` in the main callback function, + because it might not catch exceptions raised by signal handlers: if + a signal occurs while in C, the signal handler is called as soon as + possible, i.e. after entering the callback function but *before* + even executing the ``try:``. If the signal handler raises, we are + not in the ``try: except:`` yet. + + If ``onerror`` is called and returns normally, then it is assumed + that it handled the exception on its own and nothing is printed to + stderr. If ``onerror`` raises, then both tracebacks are printed. + Finally, ``onerror`` can itself provide the result value of the + callback in C, but doesn't have to: if it simply returns None---or + if ``onerror`` itself fails---then the value of ``error`` will be + used, if any. + + Note the following hack: in ``onerror``, you can access the original + callback arguments as follows. First check if ``traceback`` is not + None (it is None e.g. if the whole function ran successfully but + there was an error converting the value returned: this occurs after + the call). If ``traceback`` is not None, then + ``traceback.tb_frame`` is the frame of the outermost function, + i.e. directly the one of the function decorated with + ``@ffi.call_python()``. So you can get the value of ``argname`` in + that frame by reading ``traceback.tb_frame.f_locals['argname']``. + + +.. _Callbacks: + +Callbacks (old style) +--------------------- Here is how to make a new ``<cdata>`` object that contains a pointer to a function, where that function invokes back a Python function of @@ -440,6 +627,20 @@ ``"int(int, int)"`` is a C *function* type. Either can be specified to ffi.callback() and the result is the same. +.. warning:: + + Callbacks are provided for the ABI mode or for backward + compatibility. If you are using the out-of-line API mode, it is + recommended to use the `call_python`_ mechanism instead of + callbacks: it gives faster and cleaner code. It also avoids a + SELinux issue whereby the setting of ``deny_execmem`` must be left + to ``off`` in order to use callbacks. (A fix in cffi was + attempted---see the ``ffi_closure_alloc`` branch---but was not + merged because it creates potential memory corruption with + ``fork()``. For more information, `see here.`__) + +.. __: https://bugzilla.redhat.com/show_bug.cgi?id=1249685 + Warning: like ffi.new(), ffi.callback() returns a cdata that has ownership of its C data. (In this case, the necessary C data contains the libffi data structures to do a callback.) This means that the @@ -494,62 +695,17 @@ return 0 lib.python_callback = python_callback -Be careful when writing the Python callback function: if it returns an -object of the wrong type, or more generally raises an exception, then -the exception cannot be propagated. Instead, it is printed to stderr -and the C-level callback is made to return a default value. - -The returned value in case of errors is 0 or null by default, but can be -specified with the ``error`` keyword argument to ``ffi.callback()``: - -.. code-block:: python - - @ffi.callback("int(int, int)", error=-1) - -The exception is still printed to stderr, so this should be -used only as a last-resort solution. - Deprecated: you can also use ``ffi.callback()`` not as a decorator but directly as ``ffi.callback("int(int, int)", myfunc)``. This is discouraged: using this a style, we are more likely to forget the callback object too early, when it is still in use. -.. warning:: - - **SELinux** requires that the setting ``deny_execmem`` is left to - its default setting of ``off`` to use callbacks. A fix in cffi was - attempted (see the ``ffi_closure_alloc`` branch), but this branch is - not merged because it creates potential memory corruption with - ``fork()``. For more information, `see here.`__ +The ``ffi.callback()`` decorator also accepts the optional argument +``error``, and from CFFI version 1.2 the optional argument ``onerror``. +These two work in the same way as `described above for call_python.`__ -.. __: https://bugzilla.redhat.com/show_bug.cgi?id=1249685 +.. __: error_onerror_ -*New in version 1.2:* If you want to be sure to catch all exceptions, use -``ffi.callback(..., onerror=func)``. If an exception occurs and -``onerror`` is specified, then ``onerror(exception, exc_value, -traceback)`` is called. This is useful in some situations where -you cannot simply write ``try: except:`` in the main callback -function, because it might not catch exceptions raised by signal -handlers: if a signal occurs while in C, it will be called after -entering the main callback function but before executing the -``try:``. - -If ``onerror`` returns normally, then it is assumed that it handled -the exception on its own and nothing is printed to stderr. If -``onerror`` raises, then both tracebacks are printed. Finally, -``onerror`` can itself provide the result value of the callback in -C, but doesn't have to: if it simply returns None---or if -``onerror`` itself fails---then the value of ``error`` will be -used, if any. - -Note the following hack: in ``onerror``, you can access the original -callback arguments as follows. First check if ``traceback`` is not -None (it is None e.g. if the whole function ran successfully but -there was an error converting the value returned: this occurs after -the call). If ``traceback`` is not None, then ``traceback.tb_frame`` -is the frame of the outermost function, i.e. directly the one invoked -by the callback handler. So you can get the value of ``argname`` in -that frame by reading ``traceback.tb_frame.f_locals['argname']``. Windows: calling conventions @@ -583,7 +739,9 @@ out. In the ``cdef()``, you can also use ``WINAPI`` as equivalent to ``__stdcall``. As mentioned above, it is not needed (but doesn't hurt) to say ``WINAPI`` or ``__stdcall`` when declaring a plain -function in the ``cdef()``. +function in the ``cdef()``. (The difference can still be seen if you +take explicitly a pointer to this function with ``ffi.addressof()``, +or if the function is ``CFFI_CALL_PYTHON``.) These calling convention specifiers are accepted but ignored on any platform other than 32-bit Windows. _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit