[pypy-commit] cffi default: Add "error=..." to ffi.callback().
arigo
noreply at buildbot.pypy.org
Tue Jun 26 15:23:28 CEST 2012
Author: Armin Rigo <arigo at tunes.org>
Branch:
Changeset: r529:7cf76f8b03ba
Date: 2012-06-26 15:21 +0200
http://bitbucket.org/cffi/cffi/changeset/7cf76f8b03ba/
Log: Add "error=..." to ffi.callback().
diff --git a/c/_ffi_backend.c b/c/_ffi_backend.c
--- a/c/_ffi_backend.c
+++ b/c/_ffi_backend.c
@@ -2921,6 +2921,11 @@
&ellipsis))
return NULL;
+ if (fresult->ct_size < 0 && !(fresult->ct_flags & CT_VOID)) {
+ PyErr_SetString(PyExc_TypeError, "result type is of unknown size");
+ return NULL;
+ }
+
fct = fb_prepare_ctype(&funcbuilder, fargs, fresult, ellipsis);
if (fct == NULL)
return NULL;
@@ -2973,6 +2978,7 @@
PyObject *py_ob = PyTuple_GET_ITEM(cb_args, 1);
PyObject *py_args = NULL;
PyObject *py_res = NULL;
+ PyObject *py_rawerr;
Py_ssize_t i, n;
#define SIGNATURE(i) ((CTypeDescrObject *)PyTuple_GET_ITEM(signature, i))
@@ -3007,8 +3013,10 @@
error:
PyErr_WriteUnraisable(py_ob);
- if (SIGNATURE(0)->ct_size > 0)
- memset(result, 0, SIGNATURE(0)->ct_size);
+ if (SIGNATURE(0)->ct_size > 0) {
+ py_rawerr = PyTuple_GET_ITEM(cb_args, 2);
+ memcpy(result, PyString_AS_STRING(py_rawerr), SIGNATURE(0)->ct_size);
+ }
goto done;
}
@@ -3017,13 +3025,16 @@
static PyObject *b_callback(PyObject *self, PyObject *args)
{
- CTypeDescrObject *ct;
+ CTypeDescrObject *ct, *ctresult;
CDataObject_own_base *cdb;
- PyObject *ob;
+ PyObject *ob, *error_ob = Py_None;
+ PyObject *py_rawerr, *infotuple = NULL;
cif_description_t *cif_descr;
ffi_closure *closure;
-
- if (!PyArg_ParseTuple(args, "O!O:callback", &CTypeDescr_Type, &ct, &ob))
+ Py_ssize_t size;
+
+ if (!PyArg_ParseTuple(args, "O!O|O:callback", &CTypeDescr_Type, &ct, &ob,
+ &error_ob))
return NULL;
if (!(ct->ct_flags & CT_FUNCTIONPTR)) {
@@ -3038,6 +3049,26 @@
return NULL;
}
+ ctresult = (CTypeDescrObject *)PyTuple_GET_ITEM(ct->ct_stuff, 0);
+ size = ctresult->ct_size;
+ if (size < 0)
+ size = 0;
+ py_rawerr = PyString_FromStringAndSize(NULL, size);
+ if (py_rawerr == NULL)
+ return NULL;
+ memset(PyString_AS_STRING(py_rawerr), 0, size);
+ if (error_ob != Py_None) {
+ if (convert_from_object(PyString_AS_STRING(py_rawerr),
+ ctresult, error_ob) < 0) {
+ Py_DECREF(py_rawerr);
+ return NULL;
+ }
+ }
+ infotuple = Py_BuildValue("OOO", ct, ob, py_rawerr);
+ Py_DECREF(py_rawerr);
+ if (infotuple == NULL)
+ return NULL;
+
closure = cffi_closure_alloc();
cdb = PyObject_New(CDataObject_own_base, &CDataOwning_Type);
@@ -3055,13 +3086,12 @@
goto error;
}
if (ffi_prep_closure(closure, &cif_descr->cif,
- invoke_callback, args) != FFI_OK) {
+ invoke_callback, infotuple) != FFI_OK) {
PyErr_SetString(PyExc_SystemError,
"libffi failed to build this callback");
goto error;
}
- assert(closure->user_data == args);
- Py_INCREF(args); /* capture the tuple (CTypeDescr, Python callable) */
+ assert(closure->user_data == infotuple);
return (PyObject *)cdb;
error:
@@ -3070,6 +3100,7 @@
cffi_closure_free(closure);
else
Py_DECREF(cdb);
+ Py_XDECREF(infotuple);
return NULL;
}
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -821,17 +821,37 @@
def cb(n):
return n + 1
BFunc = new_function_type((BInt,), BInt, False)
- return callback(BFunc, cb) # 'cb' and 'BFunc' go out of scope
+ return callback(BFunc, cb, 42) # 'cb' and 'BFunc' go out of scope
f = make_callback()
assert f(-142) == -141
+def test_callback_return_type():
+ for rettype in ["signed char", "short", "int", "long", "long long",
+ "unsigned char", "unsigned short", "unsigned int",
+ "unsigned long", "unsigned long long"]:
+ BRet = new_primitive_type(rettype)
+ def cb(n):
+ return n + 1
+ BFunc = new_function_type((BRet,), BRet)
+ f = callback(BFunc, cb, 42)
+ assert f(41) == 42
+ if rettype.startswith("unsigned "):
+ min = 0
+ max = (1 << (8*sizeof(BRet))) - 1
+ else:
+ min = -(1 << (8*sizeof(BRet)-1))
+ max = (1 << (8*sizeof(BRet)-1)) - 1
+ assert f(min) == min + 1
+ assert f(max - 1) == max
+ assert f(max) == 42
+
def test_a_lot_of_callbacks():
BInt = new_primitive_type("int")
def make_callback(m):
def cb(n):
return n + m
BFunc = new_function_type((BInt,), BInt, False)
- return callback(BFunc, cb) # 'cb' and 'BFunc' go out of scope
+ return callback(BFunc, cb, 42) # 'cb' and 'BFunc' go out of scope
#
flist = [make_callback(i) for i in range(10000)]
for i, f in enumerate(flist):
diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -170,7 +170,7 @@
"""
return self._backend.buffer(cdata, size)
- def callback(self, cdecl, python_callable):
+ def callback(self, cdecl, python_callable, error=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
@@ -179,7 +179,7 @@
if not callable(python_callable):
raise TypeError("the 'python_callable' argument is not callable")
BFunc = self.typeof(cdecl)
- return self._backend.callback(BFunc, python_callable)
+ return self._backend.callback(BFunc, python_callable, error)
def getctype(self, cdecl, replace_with=''):
"""Return a string giving the C type 'cdecl', which may be itself
diff --git a/cffi/backend_ctypes.py b/cffi/backend_ctypes.py
--- a/cffi/backend_ctypes.py
+++ b/cffi/backend_ctypes.py
@@ -164,7 +164,10 @@
return self
def _get_own_repr(self):
- return self._addr_repr(self._address)
+ try:
+ return self._addr_repr(self._address)
+ except AttributeError:
+ return '???'
def _cast_to_integer(self):
return self._address
@@ -706,21 +709,33 @@
use_errno=True)
_reftypename = BResult._get_c_name('(* &)(%s)' % (nameargs,))
- def __init__(self, init):
+ def __init__(self, init, error=None):
# create a callback to the Python callable init()
+ import traceback
assert not has_varargs, "varargs not supported for callbacks"
+ if getattr(BResult, '_ctype', None) is not None:
+ error = BResult._from_ctypes(
+ BResult._create_ctype_obj(error))
+ else:
+ error = None
def callback(*args):
args2 = []
for arg, BArg in zip(args, BArgs):
args2.append(BArg._from_ctypes(arg))
- res2 = init(*args2)
- res2 = BResult._to_ctypes(res2)
+ try:
+ res2 = init(*args2)
+ except:
+ traceback.print_exc()
+ res2 = error
+ else:
+ res2 = BResult._to_ctypes(res2)
if issubclass(BResult, CTypesGenericPtr):
if res2:
res2 = ctypes.cast(res2, ctypes.c_void_p).value
# .value: http://bugs.python.org/issue1574593
else:
res2 = None
+ print repr(res2)
return res2
if issubclass(BResult, CTypesGenericPtr):
# The only pointers callbacks can return are void*s:
@@ -869,8 +884,8 @@
def cast(self, BType, source):
return BType._cast_from(source)
- def callback(self, BType, source):
- return BType(source)
+ def callback(self, BType, source, error):
+ return BType(source, error)
typeof = type
diff --git a/doc/source/index.rst b/doc/source/index.rst
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -536,8 +536,16 @@
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 null value (this is a
-random choice).
+and the C-level callback is made to return a default value.
+
+.. versionadded:: 0.2
+ The returned value in case of errors is null by default, but can be
+ specified with the ``error`` keyword argument to ``ffi.callback()``::
+
+ >>> ffi.callback("int(*)(int, int)", myfunc, error=42)
+
+ In all cases the exception is printed to stderr, so this should be
+ used only as a last-resort solution.
Miscellaneous
diff --git a/testing/backend_tests.py b/testing/backend_tests.py
--- a/testing/backend_tests.py
+++ b/testing/backend_tests.py
@@ -662,7 +662,6 @@
assert hash(a) == hash(b)
def test_callback_crash(self):
- py.test.skip("in-progress")
ffi = FFI(backend=self.Backend())
def cb(n):
raise Exception
More information about the pypy-commit
mailing list