[pypy-commit] cffi static-callback: Change the docs to use extern "Python" as discussed yesterday on irc

arigo noreply at buildbot.pypy.org
Wed Nov 18 02:06:27 EST 2015


Author: Armin Rigo <arigo at tunes.org>
Branch: static-callback
Changeset: r2412:c4190e6384da
Date: 2015-11-18 08:07 +0100
http://bitbucket.org/cffi/cffi/changeset/c4190e6384da/

Log:	Change the docs to use extern "Python" as discussed yesterday on irc
	(thanks antocuni)

diff --git a/demo/cdef_call_python.py b/demo/cdef_call_python.py
deleted file mode 100644
--- a/demo/cdef_call_python.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import cffi
-
-ffi = cffi.FFI()
-
-ffi.cdef("""
-    int add(int x, int y);
-    CFFI_CALL_PYTHON long mangle(int);
-""")
-
-ffi.set_source("_cdef_call_python_cffi", """
-
-    static long mangle(int);
-
-    static int add(int x, int y)
-    {
-        return mangle(x) + mangle(y);
-    }
-""")
-
-ffi.compile()
-
-
-from _cdef_call_python_cffi import ffi, lib
-
- at ffi.call_python("mangle")    # optional argument, default to func.__name__
-def mangle(x):
-    return x * x
-
-assert lib.add(40, 2) == 1604
diff --git a/demo/extern_python.py b/demo/extern_python.py
new file mode 100644
--- /dev/null
+++ b/demo/extern_python.py
@@ -0,0 +1,26 @@
+import cffi
+
+ffi = cffi.FFI()
+
+ffi.cdef("""int my_algo(int); extern "Python" int f(int);""")
+
+ffi.set_source("_extern_python_cffi", """
+    static int f(int);
+    static int my_algo(int n) {
+        int i, sum = 0;
+        for (i = 0; i < n; i++)
+            sum += f(n);
+        return sum;
+    }
+""")
+
+ffi.compile()
+
+
+from _extern_python_cffi import ffi, lib
+
+ at ffi.def_extern()
+def f(n):
+    return n * n
+
+assert lib.my_algo(10) == 0+1+4+9+16+25+36+49+64+81
diff --git a/doc/source/using.rst b/doc/source/using.rst
--- a/doc/source/using.rst
+++ b/doc/source/using.rst
@@ -422,10 +422,10 @@
 with ``int foo();`` really means ``int foo(void);``.)
 
 
-.. _`call_python`:
+.. _`extern "Python"`:
 
-Calling Python from C (new style)
----------------------------------
+Extern "Python" (new-style callbacks)
+-------------------------------------
 
 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
@@ -433,53 +433,64 @@
 ABI-mode solution.
 
 This is *new in version 1.4.*  Use Callbacks_ if backward compatibility
-is an issue.  (The original callbacks have more overhead and potential
-issues with libffi; see the warning__.)
+is an issue.  (The original callbacks are slower to invoke and have
+the same issue as libffi's callbacks; notably, see the warning__.
+The  new style described in the present section does not use libffi's
+callbacks at all.)
 
 .. __: Callbacks_
 
 In the builder script, declare in the cdef a function prefixed with
-``CFFI_CALL_PYTHON``::
+``extern "Python"``::
 
     ffi.cdef("""
-        typedef ... footype_t;
-        CFFI_CALL_PYTHON int my_callback(footype_t *, int);
+        extern "Python" int my_callback(int, int);
 
-        void library_function(int(*callback)(footype_t *, int));
+        void library_function(int(*callback)(int, 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::
+The function ``my_callback()`` is then implemented in Python inside
+your application's code::
 
     from _my_example import ffi, lib
 
-    @ffi.call_python()
+    @ffi.def_extern()
     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.
+You can get a ``<cdata>`` pointer-to-function object from either
+reading ``lib.my_callback``, or directly from the decorated
+``my_callback`` above.  This ``<cdata>`` can be passed to C code and
+then works like a callback: when the C code calls this function
+pointer, the Python function ``my_callback`` is called.
 
-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.
+CFFI implements this by defining ``my_callback`` as a static C
+function, written after the ``set_source()`` code.  The ``<cdata>``
+then points to this function.  What this function does is invoke the
+Python function object that was dynamically attached by
+``@ffi.def_extern()``.
 
-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::
+Each function from the cdef with ``extern "Python"`` turns into only
+one C function.  You can redefine the attached Python function by
+calling ``@ffi.def_extern()`` again, but it changes the C logic to
+call the new Python function; the old Python function is not callable
+any more and the C function pointer itself is always the same.
 
-    typedef void (*event_cb_t)(struct event *evt, void *userdata);
+Extern "Python" and "void *" arguments
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As described just before, you cannot use ``extern "Python"`` to make a
+variable number of C function pointers.  However, achieving that
+result is not possible in pure C code either.  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)(event_t *evt, void *userdata);
 
 and you register events by calling this function::
 
@@ -488,10 +499,11 @@
 Then you would write this in the build script::
 
     ffi.cdef("""
-        typedef void (*event_cb_t)(struct event *evt, void *userdata);
+        typedef ... event_t;
+        typedef void (*event_cb_t)(event_t *evt, void *userdata);
         void event_cb_register(event_cb_t cb, void *userdata);
 
-        CFFI_CALL_PYTHON void my_event_callback(struct event *, void *);
+        extern "Python" void my_event_callback(event_t *, void *);
     """)
     ffi.set_source("_demo_cffi", """
         #include <the_event_library.h>
@@ -510,7 +522,7 @@
         def process_event(self, evt):
             ...
 
-    @ffi.call_python()
+    @ffi.def_extern()
     def my_event_callback(evt, userdata):
         widget = ffi.from_handle(userdata)
         widget.process_event(evt)
@@ -520,10 +532,10 @@
 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);
+    typedef void (*event_cb_t)(widget_t *w, event_t *evt);
 
 Then you can use the ``void *`` field in the low-level
-``struct widget *`` like this::
+``widget_t *`` like this::
 
     from _demo_cffi import ffi, lib
 
@@ -532,46 +544,82 @@
             ll_widget = lib.new_widget(500, 500)
             self.ll_widget = ll_widget       # <cdata 'struct widget *'>
             userdata = ffi.new_handle(self)
+            self._userdata = userdata        # must still keep this alive!
             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()
+    @ffi.def_extern()
     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)::
+Extern "Python" accessed from C directly
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In case you want to access some ``extern "Python"`` function 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* the C code---this is needed because even the
+declaration might use types defined by ``set_source()``
+(e.g. ``event_t`` above, from the ``#include``), so it cannot be
+generated before.
 
     ffi.set_source("_demo_cffi", """
         #include <the_event_library.h>
 
-        static void my_event_callback(struct widget *, struct evt *);
+        static void my_event_callback(widget_t *, event_t *);
 
-        /* more C code which uses '&my_event_callback' or even
-           directly calls my_event_callback() */
+        /* here you can write C code which uses '&my_event_callback' */
     """)
 
-Note that ``CFFI_CALL_PYTHON`` cannot be a variadic function type
-for now (this may be implemented in the future).
+This can also be used to write custom C code which calls Python
+directly.  Here is an example (inefficient in this case, but might be
+useful if the logic in ``my_algo()`` is much more complex)::
 
-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.
+    ffi.cdef("""
+        extern "Python" int f(int);
+        int my_algo(int);
+    """)
+    ffi.set_source("_example_cffi", """
+        static int f(int);
+        static int my_algo(int n) {
+            int i, sum = 0;
+            for (i = 0; i < n; i++)
+                sum += f(n);
+            return sum;
+        }
+    """)
 
-The ``@ffi.call_python()`` decorator takes these optional arguments:
+EXtern "Python": reference
+~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-* ``name``: the name of the ``CFFI_CALL_PYTHON`` function declaration
-  from the cdef.  Defaults to the name of the Python function you
-  decorate.
+``extern "Python"`` must appear in the cdef().  Like the C++ ``extern
+"C"`` syntax, it can also be used with braces around a group of
+functions::
+
+    extern "Python" {
+        int foo(int);
+        int bar(int);
+    }
+
+The ``extern "Python"`` functions cannot be variadic for now.  This
+may be implemented in the future.
+
+The corresponding Python callback function is defined with the
+``@ffi.def_extern()`` decorator.  Be careful when writing this
+function: if it raises an exception, or tries to return an object of
+the wrong type, then the exception cannot be propagated.  Instead, the
+exception 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 below.
+
+The ``@ffi.def_extern()`` decorator takes these optional arguments:
+
+* ``name``: the name of the function as written in the cdef.  By default
+  it is taken from the name of the Python function you decorate.
 
 .. _error_onerror:
 
@@ -581,15 +629,15 @@
   solution.
 
 * ``onerror``: if you want to be sure to catch all exceptions, use
-  ``@ffi.call_python(onerror=func)``.  If an exception occurs and
+  ``@ffi.def_extern(onerror=my_handler)``.  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.
+  a signal occurs while in C, the Python signal handler is called as
+  soon as possible, which is after entering the callback function but
+  *before* executing even 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
@@ -605,8 +653,8 @@
   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
+  i.e. directly the frame of the function decorated with
+  ``@ffi.def_extern()``.  So you can get the value of ``argname`` in
   that frame by reading ``traceback.tb_frame.f_locals['argname']``.
 
 
@@ -634,7 +682,7 @@
 
     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
+    recommended to use the `extern "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
@@ -705,7 +753,7 @@
 
 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.`__
+These two work in the same way as `described above for extern "Python".`__
 
 .. __: error_onerror_
 


More information about the pypy-commit mailing list