[pypy-commit] cffi default: Partly untested: support for callbacks with different calling

arigo noreply at buildbot.pypy.org
Tue Jul 17 00:27:23 CEST 2012


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r661:cf812c61a579
Date: 2012-07-16 15:14 +0200
http://bitbucket.org/cffi/cffi/changeset/cf812c61a579/

Log:	Partly untested: support for callbacks with different calling
	convensions on Windows.

diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -3321,6 +3321,40 @@
     return NULL;
 }
 
+static PyObject *b_get_function_type_args(PyObject *self, PyObject *arg)
+{
+    CTypeDescrObject *fct = (CTypeDescrObject *)arg;
+    PyObject *x, *args, *res, *ellipsis, *conv;
+    Py_ssize_t end;
+
+    if (!CTypeDescr_Check(arg) || !(fct->ct_flags & CT_FUNCTIONPTR)) {
+        PyErr_SetString(PyExc_TypeError, "expected a 'ctype' funcptr object");
+        return NULL;
+    }
+
+    args = NULL;
+    ellipsis = NULL;
+    conv = NULL;
+    x = NULL;
+
+    end = PyTuple_GET_SIZE(fct->ct_stuff);
+    args = PyTuple_GetSlice(fct->ct_stuff, 2, end);
+    if (args == NULL)
+        goto error;
+    res = PyTuple_GET_ITEM(fct->ct_stuff, 1);
+    ellipsis = PyInt_FromLong(fct->ct_extra == NULL);
+    if (ellipsis == NULL)
+        goto error;
+    conv = PyTuple_GET_ITEM(fct->ct_stuff, 0);
+    x = PyTuple_Pack(4, args, res, ellipsis, conv);
+    /* fall-through */
+ error:
+    Py_XDECREF(args);
+    Py_XDECREF(ellipsis);
+    Py_XDECREF(conv);
+    return x;
+}
+
 static void invoke_callback(ffi_cif *cif, void *result, void **args,
                             void *userdata)
 {
@@ -3392,9 +3426,10 @@
     cif_description_t *cif_descr;
     ffi_closure *closure;
     Py_ssize_t size;
-
-    if (!PyArg_ParseTuple(args, "O!O|O:callback", &CTypeDescr_Type, &ct, &ob,
-                          &error_ob))
+    long fabi = FFI_DEFAULT_ABI;
+
+    if (!PyArg_ParseTuple(args, "O!O|Ol:callback", &CTypeDescr_Type, &ct, &ob,
+                          &error_ob, &fabi))
         return NULL;
 
     if (!(ct->ct_flags & CT_FUNCTIONPTR)) {
@@ -3864,6 +3899,7 @@
     {"new_union_type", b_new_union_type, METH_VARARGS},
     {"complete_struct_or_union", b_complete_struct_or_union, METH_VARARGS},
     {"new_function_type", b_new_function_type, METH_VARARGS},
+    {"get_function_type_args", b_get_function_type_args, METH_O},
     {"new_enum_type", b_new_enum_type, METH_VARARGS},
     {"_getfields", b__getfields, METH_O},
     {"newp", b_newp, METH_VARARGS},
@@ -4061,6 +4097,9 @@
     v = PyInt_FromLong(FFI_DEFAULT_ABI);
     if (v == NULL || PyModule_AddObject(m, "FFI_DEFAULT_ABI", v) < 0)
         return;
+    Py_INCREF(v);
+    if (PyModule_AddObject(m, "FFI_CDECL", v) < 0)  /*win32 name*/
+        return;
 
     init_errno();
 }
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -681,6 +681,19 @@
     BFunc = new_function_type((BStruct,), BShort, False)
     assert repr(BFunc) == "<ctype 'short(*)(struct foo)'>"
 
+def test_get_function_type_args():
+    BChar = new_primitive_type("char")
+    BShort = new_primitive_type("short")
+    BStruct = new_struct_type("foo")
+    complete_struct_or_union(BStruct, [('a1', BChar, -1),
+                                       ('a2', BShort, -1)])
+    BFunc = new_function_type((BStruct,), BShort, False)
+    a, b, c, d = get_function_type_args(BFunc)
+    assert a == (BStruct,)
+    assert b == BShort
+    assert c == False
+    assert d == FFI_DEFAULT_ABI
+
 def test_function_void_result():
     BVoid = new_void_type()
     BInt = new_primitive_type("int")
diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -182,7 +182,7 @@
         """
         return self._backend.buffer(cdata, size)
 
-    def callback(self, cdecl, python_callable, error=None):
+    def callback(self, cdecl, python_callable, error=None, conv=None):
         """Return a callback object.  'cdecl' must name a C function pointer
         type.  The callback invokes the specified 'python_callable'.
         Important: the callback object must be manually kept alive for as
@@ -191,8 +191,32 @@
         if not callable(python_callable):
             raise TypeError("the 'python_callable' argument is not callable")
         BFunc = self.typeof(cdecl, consider_function_as_funcptr=True)
+        if conv is not None:
+            BFunc = self._functype_with_conv(BFunc, conv)
         return self._backend.callback(BFunc, python_callable, error)
 
+    def _functype_with_conv(self, BFunc, conv):
+        abiname = '%s' % (conv.upper(),)
+        try:
+            abi = getattr(self._backend, 'FFI_' + abiname)
+        except AttributeError:
+            raise ValueError("the calling convention %r is unknown to "
+                             "the backend" % (abiname,))
+        if abi == self._backend.FFI_DEFAULT_ABI:
+            return BFunc
+        # xxx only for _cffi_backend.c so far
+        try:
+            bfunc_abi_cache = self._bfunc_abi_cache
+            return bfunc_abi_cache[BFunc, abi]
+        except AttributeError:
+            bfunc_abi_cache = self._bfunc_abi_cache = {}
+        except KeyError:
+            pass
+        args, res, ellipsis, _ = self._backend.get_function_type_args(BFunc)
+        result = self._backend.new_function_type(args, res, ellipsis, abi)
+        bfunc_abi_cache[BFunc, abi] = result
+        return result
+
     def getctype(self, cdecl, replace_with=''):
         """Return a string giving the C type 'cdecl', which may be itself
         a string or a <ctype> object.  If 'replace_with' is given, it gives
diff --git a/doc/source/index.rst b/doc/source/index.rst
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -714,9 +714,14 @@
 
 Note that callbacks of a variadic function type are not supported.
 
-Windows: you can't yet specify the calling convention of callbacks.
-(For regular calls, the correct calling convention should be
-automatically inferred by the C backend.)
+Windows: for regular calls, the correct calling convention should be
+automatically inferred by the C backend, but that doesn't work for
+callbacks.  The default calling convention is "cdecl", like in C;
+if needed, you must force the calling convention with the keyword
+argument ``conv``::
+
+    ffi.callback("int(*)(int, int)", myfunc, conv="stdcall")
+    ffi.callback("int(*)(int, int)", myfunc, conv="cdecl")  # default
 
 Be careful when writing the Python callback function: if it returns an
 object of the wrong type, or more generally raises an exception, then
@@ -737,7 +742,9 @@
 
 ``ffi.errno``: the value of ``errno`` received from the most recent C call
 in this thread, and passed to the following C call, is available via
-reads and writes of the property ``ffi.errno``.
+reads and writes of the property ``ffi.errno``.  On Windows we also save
+and restore the ``GetLastError()`` value, but to access it you need to
+declare and call the ``GetLastError()`` function as usual.
 
 ``ffi.buffer(pointer, [size])``: return a read-write buffer object that
 references the raw C data pointed to by the given 'cdata', of 'size'


More information about the pypy-commit mailing list