Author: Armin Rigo <[email protected]>
Branch:
Changeset: r1187:8a27b7fa299a
Date: 2013-03-07 22:58 +0100
http://bitbucket.org/cffi/cffi/changeset/8a27b7fa299a/
Log: Kill the historical non-decorator version of ffi.callback(), as a
motivation for doing "the right thing" (see new paragraph in docs).
diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -231,13 +231,13 @@
"""
return self._backend.buffer(cdata, size)
- def callback(self, cdecl, python_callable=None, error=None):
- """Return a callback object or a decorator making such a
- callback object. 'cdecl' must name a C function pointer type.
- The callback invokes the specified 'python_callable' (which may
- be provided either directly or via a decorator). Important: the
- callback object must be manually kept alive for as long as the
- callback may be invoked from the C level.
+ def callback(self, cdecl, error=None):
+ """Return a a decorator making a callback object. 'cdecl' must
+ name a C function or function pointer type. The decorated
+ Python function is turned into a <cdata> of the specified
+ function pointer type. Important: the callback object must be
+ manually kept alive for as long as the callback may be invoked
+ from the C level.
"""
def callback_decorator_wrap(python_callable):
if not callable(python_callable):
@@ -246,10 +246,10 @@
return self._backend.callback(cdecl, python_callable, error)
if isinstance(cdecl, basestring):
cdecl = self._typeof(cdecl, consider_function_as_funcptr=True)
- if python_callable is None:
- return callback_decorator_wrap # decorator mode
- else:
- return callback_decorator_wrap(python_callable) # direct mode
+ if isinstance(error, types.FunctionType):
+ raise TypeError("Not supported any more: ffi.callback('...', fn)."
+ " Use the decorator form: @ffi.callback('...')")
+ return callback_decorator_wrap
def getctype(self, cdecl, replace_with=''):
"""Return a string giving the C type 'cdecl', which may be itself
diff --git a/doc/source/index.rst b/doc/source/index.rst
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -983,23 +983,21 @@
passed as callbacks. To make new C callback objects that will invoke a
Python function, you need to use::
- >>> def myfunc(x, y):
- ... return x + y
- ...
- >>> ffi.callback("int(int, int)", myfunc)
- <cdata 'int(*)(int, int)' calling <function myfunc at 0xf757bbc4>>
-
-.. versionadded:: 0.4
- Or equivalently as a decorator:
-
>>> @ffi.callback("int(int, int)")
... def myfunc(x, y):
... return x + y
+ ...
+ >>> myfunc
+ <cdata 'int(*)(int, int)' calling <function myfunc at 0xf757bbc4>>
Note that you can also use a C *function pointer* type like ``"int(*)(int,
int)"`` (as opposed to a C *function* type like ``"int(int, int)"``). It
is equivalent here.
+.. versionchanged:: 0.6
+ ``ffi.callback()`` is now only available as a decorator (see later
+ for the motivation).
+
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
@@ -1009,6 +1007,18 @@
the callback to remain valid forever, store the object in a fresh global
variable somewhere.)
+Generally, C APIs define callbacks taking a general ``void *`` argument.
+You can make good use of it with CFFI as well, in order to avoid
+creating a large number of callback objects. You would create instead
+only one (or a small number of) callback as global functions. You
+cannot give a "pointer" to a real Python object as the ``void *``
+argument, but instead you can use its id() casted to ``void *``. You
+store in a ``weakref.WeakValueDictionary`` the mapping that goes from
+the id() back to the full object. When the callback is invoked, you
+look up the full object in the mapping, gracefully ignoring it if it was
+already freed. (In order to encourage this approach, ``ffi.callback()``
+can only be used as a decorator from version 0.6.)
+
Note that callbacks of a variadic function type are not supported. A
workaround is to add custom C code. In the following example, a
callback gets a first argument that counts how many extra ``int``
diff --git a/testing/backend_tests.py b/testing/backend_tests.py
--- a/testing/backend_tests.py
+++ b/testing/backend_tests.py
@@ -658,11 +658,11 @@
def test_functionptr_simple(self):
ffi = FFI(backend=self.Backend())
- py.test.raises(TypeError, ffi.callback, "int(*)(int)", 0)
+ py.test.raises(TypeError, ffi.callback("int(*)(int)"), 0)
def cb(n):
return n + 1
cb.__qualname__ = 'cb'
- p = ffi.callback("int(*)(int)", cb)
+ p = ffi.callback("int(*)(int)")(cb)
res = p(41) # calling an 'int(*)(int)', i.e. a function pointer
assert res == 42 and type(res) is int
res = p(ffi.cast("int", -41))
@@ -688,47 +688,47 @@
def test_functionptr_voidptr_return(self):
ffi = FFI(backend=self.Backend())
- def cb():
+ @ffi.callback("void*(*)()")
+ def p():
return ffi.NULL
- p = ffi.callback("void*(*)()", cb)
res = p()
assert res is not None
assert res == ffi.NULL
int_ptr = ffi.new('int*')
void_ptr = ffi.cast('void*', int_ptr)
- def cb():
+ @ffi.callback("void*(*)()")
+ def p():
return void_ptr
- p = ffi.callback("void*(*)()", cb)
res = p()
assert res == void_ptr
def test_functionptr_intptr_return(self):
ffi = FFI(backend=self.Backend())
- def cb():
+ @ffi.callback("int*(*)()")
+ def p():
return ffi.NULL
- p = ffi.callback("int*(*)()", cb)
res = p()
assert res == ffi.NULL
int_ptr = ffi.new('int*')
- def cb():
+ @ffi.callback("int*(*)()")
+ def p():
return int_ptr
- p = ffi.callback("int*(*)()", cb)
res = p()
assert repr(res).startswith("<cdata 'int *' 0x")
assert res == int_ptr
int_array_ptr = ffi.new('int[1]')
- def cb():
+ @ffi.callback("int*(*)()")
+ def p():
return int_array_ptr
- p = ffi.callback("int*(*)()", cb)
res = p()
assert repr(res).startswith("<cdata 'int *' 0x")
assert res == int_array_ptr
def test_functionptr_void_return(self):
ffi = FFI(backend=self.Backend())
- def foo():
+ @ffi.callback("void foo()")
+ def foo_cb():
pass
- foo_cb = ffi.callback("void foo()", foo)
result = foo_cb()
assert result is None
@@ -793,9 +793,9 @@
def test_cast_functionptr_and_int(self):
ffi = FFI(backend=self.Backend())
- def cb(n):
+ @ffi.callback("int(*)(int)")
+ def a(n):
return n + 1
- a = ffi.callback("int(*)(int)", cb)
p = ffi.cast("void *", a)
assert p
b = ffi.cast("int(*)(int)", p)
@@ -805,18 +805,24 @@
def test_callback_crash(self):
ffi = FFI(backend=self.Backend())
- def cb(n):
+ @ffi.callback("int(*)(int)", error=42)
+ def a(n):
raise Exception
- a = ffi.callback("int(*)(int)", cb, error=42)
res = a(1) # and the error reported to stderr
assert res == 42
+ #
+ @ffi.callback("int(int)", 46)
+ def a(n):
+ raise Exception
+ res = a(2) # and the error reported to stderr
+ assert res == 46
def test_structptr_argument(self):
ffi = FFI(backend=self.Backend())
ffi.cdef("struct foo_s { int a, b; };")
- def cb(p):
+ @ffi.callback("int(*)(struct foo_s[])")
+ def a(p):
return p[0].a * 1000 + p[0].b * 100 + p[1].a * 10 + p[1].b
- a = ffi.callback("int(*)(struct foo_s[])", cb)
res = a([[5, 6], {'a': 7, 'b': 8}])
assert res == 5678
res = a([[5], {'b': 8}])
@@ -826,10 +832,10 @@
ffi = FFI(backend=self.Backend())
ffi.cdef("struct foo_s { int a, b; };")
seen = []
- def cb(argv):
+ @ffi.callback("void(*)(char *[])")
+ def a(argv):
seen.append(ffi.string(argv[0]))
seen.append(ffi.string(argv[1]))
- a = ffi.callback("void(*)(char *[])", cb)
a([ffi.new("char[]", b"foobar"), ffi.new("char[]", b"baz")])
assert seen == [b"foobar", b"baz"]
@@ -1225,9 +1231,9 @@
assert a[1] == ffi.NULL
py.test.raises(TypeError, ffi.cast, "int(*)(int)[5]", 0)
#
- def cb(n):
+ @ffi.callback("int(*)(int)")
+ def f(n):
return n + 1
- f = ffi.callback("int(*)(int)", cb)
a = ffi.new("int(*[5])(int)", [f, f])
assert a[1](42) == 43
@@ -1235,23 +1241,23 @@
# In C, function arguments can be declared with a function type,
# which is automatically replaced with the ptr-to-function type.
ffi = FFI(backend=self.Backend())
- def cb(a, b):
+ @ffi.callback("char cb(char, char)")
+ def f(a, b):
return chr(ord(a) + ord(b)).encode()
- f = ffi.callback("char cb(char, char)", cb)
assert f(b'A', b'\x01') == b'B'
+ @ffi.callback("char g(char cb(char, char))")
def g(callback):
return callback(b'A', b'\x01')
- g = ffi.callback("char g(char cb(char, char))", g)
assert g(f) == b'B'
def test_vararg_callback(self):
py.test.skip("callback with '...'")
ffi = FFI(backend=self.Backend())
- def cb(i, va_list):
+ @ffi.callback("long long cb(long i, ...)")
+ def f(i, va_list):
j = ffi.va_arg(va_list, "int")
k = ffi.va_arg(va_list, "long long")
return i * 2 + j * 3 + k * 5
- f = ffi.callback("long long cb(long i, ...)", cb)
res = f(10, ffi.cast("int", 100), ffi.cast("long long", 1000))
assert res == 20 + 300 + 5000
@@ -1265,6 +1271,10 @@
assert cb(-100, -10) == -90
sz = ffi.sizeof("long")
assert cb((1 << (sz*8-1)) - 1, -10) == 42
+ #
+ py.test.raises(TypeError, ffi.callback, "int(int)", lambda n: n+1)
+ py.test.raises(TypeError, ffi.callback, "int(int)", lambda n: n+1,
+ error=42)
def test_unique_types(self):
ffi1 = FFI(backend=self.Backend())
diff --git a/testing/test_ffi_backend.py b/testing/test_ffi_backend.py
--- a/testing/test_ffi_backend.py
+++ b/testing/test_ffi_backend.py
@@ -17,7 +17,7 @@
ffi = FFI(backend=self.Backend())
ffi.cdef("struct foo_s { int a,b,c,d,e; int x:1; };")
e = py.test.raises(NotImplementedError, ffi.callback,
- "struct foo_s foo(void)", lambda: 42)
+ "struct foo_s foo(void)")
assert str(e.value) == ("<struct foo_s(*)(void)>: "
"cannot pass as argument or return value a struct with bit fields")
diff --git a/testing/test_function.py b/testing/test_function.py
--- a/testing/test_function.py
+++ b/testing/test_function.py
@@ -209,8 +209,8 @@
def cb(charp):
assert repr(charp).startswith("<cdata 'char *' 0x")
return 42
- fptr = ffi.callback("int(*)(const char *txt)", cb)
- assert fptr != ffi.callback("int(*)(const char *)", cb)
+ fptr = ffi.callback("int(*)(const char *txt)")(cb)
+ assert fptr != ffi.callback("int(*)(const char *)")(cb)
assert repr(fptr) == "<cdata 'int(*)(char *)' calling %r>" % (cb,)
res = fptr(b"Hello")
assert res == 42
@@ -232,9 +232,9 @@
def test_callback_returning_void(self):
ffi = FFI(backend=self.Backend())
for returnvalue in [None, 42]:
- def cb():
+ @ffi.callback("void(*)(void)")
+ def fptr():
return returnvalue
- fptr = ffi.callback("void(*)(void)", cb)
old_stderr = sys.stderr
try:
sys.stderr = StringIO()
diff --git a/testing/test_unicode_literals.py b/testing/test_unicode_literals.py
--- a/testing/test_unicode_literals.py
+++ b/testing/test_unicode_literals.py
@@ -67,6 +67,7 @@
def test_callback():
ffi = FFI()
- cb = ffi.callback("int(int)", # unicode literal
- lambda x: x + 42)
+ @ffi.callback("int(int)") # unicode literal
+ def cb(x):
+ return x + 42
assert cb(5) == 47
diff --git a/testing/test_verify.py b/testing/test_verify.py
--- a/testing/test_verify.py
+++ b/testing/test_verify.py
@@ -739,7 +739,10 @@
""")
lib.reset_cb()
assert lib.foo(6) == 41
- my_callback = ffi.callback("int(*)(int)", lambda n: n * 222)
+ #
+ @ffi.callback("int(*)(int)")
+ def my_callback(n):
+ return n * 222
lib.cb = my_callback
assert lib.foo(4) == 887
@@ -757,7 +760,10 @@
""")
lib.reset_cb()
assert lib.foo(6) == 41
- my_callback = ffi.callback("int(*)(int)", lambda n: n * 222)
+ #
+ @ffi.callback("int(int)")
+ def my_callback(n):
+ return n * 222
lib.cb = my_callback
assert lib.foo(4) == 887
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit