[pypy-commit] cffi static-callback: Write documentation about ffi.call_python().
arigo
noreply at buildbot.pypy.org
Sun Nov 15 05:06:39 EST 2015
Author: Armin Rigo <arigo at 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.
More information about the pypy-commit
mailing list