Using CFFI together with ctypes

674 views
Skip to first unread message

Андрей Парамонов

unread,
Dec 6, 2016, 9:45:03 AM12/6/16
to python-cffi
Hello!

I have a pretty long (and nice) experience with Python ctypes.
But this time I decided to try something new and use cffi to parse complex struct definitions. It worked fine, and I was able to instantiate a struct:

    spec = ffi.new('struct tagAMDIS_ELUSPEC*')

However, I couldn't easily use this object to receive results of the function call. I tried the obvious

    lib.AMDIS_ReadResFileSpec(key, i, spec)

but Python said

ctypes.ArgumentError: argument 3: <class 'TypeError'>: Don't know how to convert parameter 3

It appears that CFFI cdata is incompatible with ctypes cdata :-(
The following worked but is ugly and inefficient:

    ctypes_spec = (c_byte*len(ffi.buffer(spec)))()
    lib.AMDIS_ReadResFileSpec(key, i, byref(ctypes_spec))
    ffi.memmove(spec, ctypes_spec, len(ffi.buffer(spec)))

What is the right way to use CFFI structs in ctypes function calls?

Best wishes,
Andrey Paramonov

Armin Rigo

unread,
Dec 6, 2016, 1:04:14 PM12/6/16
to pytho...@googlegroups.com
Hi,

On 6 December 2016 at 15:45, Андрей Парамонов <cmr....@gmail.com> wrote:
> What is the right way to use CFFI structs in ctypes function calls?

The most "correct" way would be to drop ctypes completely, but I
suppose that's not the answer you're looking for :-) If you want a
different hack that should work: ctypes sometimes accepts buffer-like
objects as argument, so maybe something like ``ffi.buffer(spec)`` can
be passed directly to the ctypes call. Try with or without
``byref()`` around it (I didn't try and I'm not 100% sure).


A bientôt,

Armin.

Андрей Парамонов

unread,
Dec 7, 2016, 7:17:39 AM12/7/16
to pytho...@googlegroups.com
I discovered that memory address of CFFI struct can be obtained via
int(ffi.cast('int', spec))
which doesn't require copying and can be directly passed to ctypes library calls, or cast to needed ctypes type.

It's strange that currently CFFI CData cannot be easily converted to ctypes object, but hopefully that will come in future versions ;-)

Best wishes,
Andrey Paramonov

Armin Rigo

unread,
Dec 7, 2016, 10:53:05 AM12/7/16
to pytho...@googlegroups.com
Hi,

On 7 December 2016 at 13:16, Андрей Парамонов <cmr....@gmail.com> wrote:
> I discovered that memory address of CFFI struct can be obtained via
> int(ffi.cast('int', spec))

No, this casts to the C type 'int', which is too small to contain a
pointer. Try int(ffi.cast("intptr_t", spec)) if you want to do that.

> It's strange that currently CFFI CData cannot be easily converted to ctypes
> object, but hopefully that will come in future versions ;-)

I gave an answer to that in my previous e-mail already. If it doesn't
work, please tell.


A bientôt,

Armin.

Андрей Парамонов

unread,
Dec 8, 2016, 10:43:27 AM12/8/16
to pytho...@googlegroups.com
2016-12-07 18:52 GMT+03:00 Armin Rigo <armin...@gmail.com>:
Hi,

On 7 December 2016 at 13:16, Андрей Парамонов <cmr....@gmail.com> wrote:
> I discovered that memory address of CFFI struct can be obtained via
> int(ffi.cast('int', spec))

No, this casts to the C type 'int', which is too small to contain a
pointer.  Try int(ffi.cast("intptr_t", spec)) if you want to do that.

On 64-bit systems I presume? It worked for me on 32-bit process.
 
> It's strange that currently CFFI CData cannot be easily converted to ctypes
> object, but hopefully that will come in future versions ;-)

I gave an answer to that in my previous e-mail already.  If it doesn't
work, please tell.

Your suggestion to use ffi.buffer didn't work, unfortunately. With or without byref() I get, respectively:

ctypes.ArgumentError: argument 3: <class 'TypeError'>: Don't know how to convert parameter 3

TypeError: byref() argument must be a ctypes instance, not '_cffi_backend.buffer' 

(It would be really surprising for me if ctypes could consume generic buffer object!)

Best wishes,
Andrey Paramonov

Armin Rigo

unread,
Dec 8, 2016, 12:21:48 PM12/8/16
to pytho...@googlegroups.com
Hi,

On 8 December 2016 at 16:42, Андрей Парамонов <cmr....@gmail.com> wrote:
> On 64-bit systems I presume? It worked for me on 32-bit process.

Yes, it works on 32-bit because "int" is an integer type that happens
to be as big as pointers on 32-bit.

> Your suggestion to use ffi.buffer didn't work, unfortunately. With or
> without byref() I get, respectively:

Indeed. I could only get it to work with:

ctypes.cast(int(ffi.cast("intptr_t", p)), ctypes.c_void_p)

which is quite a number of casts but at least doesn't require a
complete memmove.

> (It would be really surprising for me if ctypes could consume generic buffer
> object!)

I've stopped long ago being surprised by what ctypes can and can't do.
It can do quite an unexpected number of things---just apparently not
this one. In general I tend to think about ctypes as an interface
that tries to do everything under the sun for you, which works in 95%
of the cases and leaves you wondering why the remaining 5% crash
obscurely. CFFI is not necessarily better, because it crashes in
other ways, but it strives to be more minimal---so the crashes you get
with CFFI are typically crashes that you'd also get if you were
writing the same thing in C. E.g. you "should know" from C that
casting a pointer to "int" is the wrong thing to do :-)


A bientôt,

Armin.
Reply all
Reply to author
Forward
0 new messages