Skip to content

Commit

Permalink
Pass API-mode function wrappers to places that expect a cdata pointer (
Browse files Browse the repository at this point in the history
…#42)

* pass API-mode function wrappers to places that expect a cdata pointer

* bug fix

* update the documentation
  • Loading branch information
arigo authored Dec 20, 2023
1 parent 520e5f7 commit 758a88f
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 19 deletions.
31 changes: 27 additions & 4 deletions doc/source/ref.rst
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,16 @@ and in C, where ``&array[index]`` is just ``array + index``.
3. ``ffi.addressof(<library>, "name")`` returns the address of the
named function or global variable from the given library object.
For functions, it returns a regular cdata
object containing a pointer to the function.
object containing a pointer to the function. Note that in API
mode, this is not the same as just writing `lib.funcname`: the latter
returns a special object that (before version 1.17) can
mostly only be called. *New in version 1.17:* you can now use
`lib.funcname` in many places where a `<cdata>` object was required,
so using `ffi.addressof(lib, "funcname")` is generally not needed any
more. For example, you can now pass `lib.funcname` as a callback to
a C function call, or write it inside a C structure field of the
correct pointer-to-function type, or use `ffi.cast()` or
`ffi.typeof()` on it.

Note that the case 1. cannot be used to take the address of a
primitive or pointer, but only a struct or union. It would be
Expand Down Expand Up @@ -807,15 +816,15 @@ allowed.
+---------------+------------------------+ | |
| ``void *`` | another <cdata> with | | |
| | any pointer or array | | |
| | type | | |
| | type, or `[9]` | | |
+---------------+------------------------+ +----------------+
| pointers to | same as pointers | | ``[]``, ``+``, |
| structure or | | | ``-``, bool(), |
| union | | | and read/write |
| | | | struct fields |
+---------------+------------------------+ +----------------+
| function | same as pointers | | bool(), |
| pointers | | | call `[2]` |
| function | same as pointers, | | bool(), |
| pointers | or `[9]` | | call `[2]` |
+---------------+------------------------+------------------+----------------+
| arrays | a list or tuple of | a <cdata> |len(), iter(), |
| | items | |``[]`` `[4]`, |
Expand Down Expand Up @@ -946,6 +955,20 @@ allowed.

See `Unicode character types`_ below.

`[9]` API-mode function from `lib.myfunc`:

In API mode, when you get a function from a C library by writing
`fn = lib.myfunc`, you get an object of a special type for performance
reasons, instead of a `<cdata 'C-function-type'>`. Before version 1.17
you could only call such objects. You could write
`ffi.addressof(lib, "myfunc")` in order to get a real `<cdata>` object,
based on the idea that in these cases in C you'd usually write `&myfunc`
instead of `myfunc`. *New in version 1.17:* the special object
`lib.myfunc` can now be passed in many places where CFFI expects
a regular `<cdata>` object. For example, you can now pass
it as a callback to a C function call, or write it inside a C
structure field of the correct pointer-to-function type, or use
`ffi.cast()` or `ffi.typeof()` on it.

.. _file:

Expand Down
34 changes: 32 additions & 2 deletions src/c/_cffi_backend.c
Original file line number Diff line number Diff line change
Expand Up @@ -1613,6 +1613,8 @@ convert_struct_from_object(char *data, CTypeDescrObject *ct, PyObject *init,
return _convert_error(init, ct, expected);
}

static PyObject* try_extract_directfnptr(PyObject *x); /* forward */

#ifdef __GNUC__
# if __GNUC__ >= 4
/* Don't go inlining this huge function. Needed because occasionally
Expand Down Expand Up @@ -1640,9 +1642,18 @@ convert_from_object(char *data, CTypeDescrObject *ct, PyObject *init)
CTypeDescrObject *ctinit;

if (!CData_Check(init)) {
expected = "cdata pointer";
goto cannot_convert;
PyObject *func_cdata = try_extract_directfnptr(init);
if (func_cdata != NULL && CData_Check(func_cdata)) {
init = func_cdata;
}
else {
if (PyErr_Occurred())
return -1;
expected = "cdata pointer";
goto cannot_convert;
}
}

ctinit = ((CDataObject *)init)->c_type;
if (!(ctinit->ct_flags & (CT_POINTER|CT_FUNCTIONPTR))) {
if (ctinit->ct_flags & CT_ARRAY)
Expand Down Expand Up @@ -4081,10 +4092,20 @@ static CDataObject *cast_to_integer_or_char(CTypeDescrObject *ct, PyObject *ob)
value = res;
}
else {
if (PyCFunction_Check(ob)) {
PyObject *func_cdata = try_extract_directfnptr(ob);
if (func_cdata != NULL && CData_Check(func_cdata)) {
value = (Py_intptr_t)((CDataObject *)func_cdata)->c_data;
goto got_value;
}
if (PyErr_Occurred())
return NULL;
}
value = _my_PyLong_AsUnsignedLongLong(ob, 0);
if (value == (unsigned PY_LONG_LONG)-1 && PyErr_Occurred())
return NULL;
}
got_value:
if (ct->ct_flags & CT_IS_BOOL)
value = !!value;
cd = _new_casted_primitive(ct);
Expand Down Expand Up @@ -4141,6 +4162,15 @@ static PyObject *do_cast(CTypeDescrObject *ct, PyObject *ob)
return new_simple_cdata(cdsrc->c_data, ct);
}
}
if (PyCFunction_Check(ob)) {
PyObject *func_cdata = try_extract_directfnptr(ob);
if (func_cdata != NULL && CData_Check(func_cdata)) {
char *ptr = ((CDataObject *)func_cdata)->c_data;
return new_simple_cdata(ptr, ct);
}
if (PyErr_Occurred())
return NULL;
}
if ((ct->ct_flags & CT_POINTER) &&
(ct->ct_itemdescr->ct_flags & CT_IS_FILE) &&
PyFile_Check(ob)) {
Expand Down
47 changes: 34 additions & 13 deletions src/c/lib_obj.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
struct CPyExtFunc_s {
PyMethodDef md;
void *direct_fn;
PyObject *direct_fn_cdata;
int type_index;
char doc[1];
};
Expand Down Expand Up @@ -662,6 +663,31 @@ static LibObject *lib_internal_new(FFIObject *ffi, const char *module_name,
return NULL;
}

static PyObject* try_extract_directfnptr(PyObject *x)
{
/* returns: borrowed ref or NULL */
LibObject *lib;
PyObject *ct;
struct CPyExtFunc_s *exf = _cpyextfunc_get(x);
if (exf == NULL)
return NULL; /* wrong type */
if (exf->direct_fn_cdata != NULL)
return exf->direct_fn_cdata; /* common case: cached */

if (exf->direct_fn == NULL)
return x; /* backward compatibility: no direct_fn */

lib = (LibObject *)PyCFunction_GET_SELF(x);
ct = _cpyextfunc_type(lib, exf);
if (ct == NULL)
return NULL; /* error */

x = new_simple_cdata(exf->direct_fn, (CTypeDescrObject *)ct);
Py_DECREF(ct);
exf->direct_fn_cdata = x; /* caches x, which becomes immortal like exf */
return x;
}

static PyObject *address_of_global_var(PyObject *args)
{
LibObject *lib;
Expand All @@ -683,20 +709,15 @@ static PyObject *address_of_global_var(PyObject *args)
return cg_addressof_global_var((GlobSupportObject *)x);
}
else {
struct CPyExtFunc_s *exf = _cpyextfunc_get(x);
if (exf != NULL) { /* an OP_CPYTHON_BLTN: '&func' returns a cdata */
PyObject *ct;
if (exf->direct_fn == NULL) {
Py_INCREF(x); /* backward compatibility */
return x;
}
ct = _cpyextfunc_type(lib, exf);
if (ct == NULL)
return NULL;
x = new_simple_cdata(exf->direct_fn, (CTypeDescrObject *)ct);
Py_DECREF(ct);
return x;
PyObject *func_cdata = try_extract_directfnptr(x);
if (func_cdata != NULL) {
/* an OP_CPYTHON_BLTN: '&func' returns a cdata */
Py_INCREF(func_cdata);
return func_cdata;
}
if (PyErr_Occurred())
return NULL;

if (CData_Check(x) && /* a constant functionptr cdata: 'f == &f' */
(((CDataObject *)x)->c_type->ct_flags & CT_FUNCTIONPTR) != 0) {
Py_INCREF(x);
Expand Down
47 changes: 47 additions & 0 deletions testing/cffi1/test_recompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2493,3 +2493,50 @@ def test_passing_large_list():
arg = list(range(20000000))
lib.passing_large_list(arg)
# assert did not segfault

def test_convert_api_mode_builtin_function_to_cdata():
ffi = FFI()
ffi.cdef(
"""struct s { int x; };
struct s add1(struct s); struct s add2(struct s);
int mycall(struct s(*)(struct s)); int mycall2(void *);""")
lib = verify(ffi, "test_convert_api_mode_builtin_function_to_cdata", """
struct s { int x; };
static struct s add1(struct s a) {
struct s r; r.x = a.x + 1; return r;
}
static struct s add2(struct s a) {
struct s r; r.x = a.x + 2; return r;
}
static int mycall(struct s(*cb)(struct s)) {
struct s a; a.x = 100;
return cb(a).x;
}
static int mycall2(void *cb) {
struct s a; a.x = 200;
return ((struct s(*)(struct s))cb)(a).x;
}
""")
s_ptr = ffi.new("struct s *", [42])
s = s_ptr[0]
assert lib.add1(s).x == 43
assert lib.add2(s).x == 44
assert lib.mycall(lib.add1) == 101
assert lib.mycall(lib.add2) == 102
assert lib.mycall2(lib.add1) == 201
assert lib.mycall2(lib.add2) == 202
s.x = -42
my_array = ffi.new("struct s(*[2])(struct s)")
my_array[0] = lib.add1
my_array[1] = lib.add2
assert my_array[0](s).x == -41
assert my_array[1](s).x == -40
s.x = 84
p = ffi.cast("void *", lib.add1)
assert ffi.cast("struct s(*)(struct s)", p)(s).x == 85
q = ffi.cast("intptr_t", lib.add2)
assert ffi.cast("struct s(*)(struct s)", q)(s).x == 86
s.x = 300
my_array_2 = ffi.new("void *[]", [lib.add1, lib.add2])
assert ffi.cast("struct s(*)(struct s)", my_array_2[1])(s).x == 302
assert ffi.typeof(lib.add1) == ffi.typeof("struct s(*)(struct s)")

0 comments on commit 758a88f

Please sign in to comment.