[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