47 releases (12 breaking)

0.13.8 Nov 8, 2021
0.12.0 Oct 4, 2021
0.5.1 Jul 30, 2021

#323 in FFI

MIT license

225KB
3.5K SLoC

Generates CPython CFFI bindings for Interoptopus.

⚠️ Deprecation Notice

This backend is deprecated. Use the interoptopus_backend_cpython instead, which has better Python compatibility and generates cleaner code.

Usage

Assuming you have written a crate containing your FFI logic called example_library_ffi and want to generate CPython CFFI bindings for Python 3.7+, follow the instructions below.

Inside Your Library

Add Interoptopus attributes to the library you have written, and define an inventory function listing all symbols you wish to export. An overview of all supported constructs can be found in the reference project.

use interoptopus::{ffi_function, ffi_type};

#[ffi_type]
#[repr(C)]
pub struct Vec2 {
    pub x: f32,
    pub y: f32,
}

#[ffi_function]
#[no_mangle]
pub extern "C" fn my_function(input: Vec2) -> Vec2 {
    input
}

interoptopus::inventory!(my_inventory, [], [my_function], [], []);

Add these to your Cargo.toml so the attributes and the binding generator can be found (replace ... with the latest version):

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
interoptopus = "..."
interoptopus_backend_cpython_cffi = "..."

Create a unit test in tests/bindings.rs which will generate your bindings when run with cargo test. In real projects you might want to add this code to another crate instead:

use interoptopus::util::NamespaceMappings;
use interoptopus::{Error, Interop};

#[test]
fn bindings_cpython_cffi() -> Result<(), Error> {
    use interoptopus_backend_cpython_cffi::{Config, Generator};

    let library = example_library_ffi::my_inventory();

    Generator::new(Config::default(), library)
        .write_file("bindings/python/example_library.py")?;

    Ok(())
}

Now run cargo test.

If anything is unclear you can find a working sample on Github.

Generated Output

The output below is what this backend might generate. Have a look at the Config struct if you want to customize something. If you really don't like how something is generated it is easy to create your own.

from cffi import FFI

api_definition = """
typedef struct cffi_vec2
    {
    float x;
    float y;
    } cffi_vec2;


cffi_vec2 my_function(cffi_vec2 input);
"""

ffi = FFI()
ffi.cdef(api_definition)
_api = None

def init_api(dll):
    """Initializes this library, call with path to DLL."""
    global _api
    _api = ffi.dlopen(dll)


class Vec2(object):
    """ A simple type in our FFI layer."""
    def __init__(self):
        global _api, ffi
        self._ctx = ffi.new("cffi_vec2[]", 1)

    def array(n):
        global _api, ffi
        return ffi.new("cffi_vec2[]", n)

    def ptr(self):
        return self._ctx

    @property
    def x(self):
        """"""
        return self._ctx[0].x

    @x.setter
    def x(self, value):
        self._ptr_x = value
        self._ctx[0].x = value

    @property
    def y(self):
        """"""
        return self._ctx[0].y

    @y.setter
    def y(self, value):
        self._ptr_y = value
        self._ctx[0].y = value


class raw:
    """Raw access to all exported functions."""
    def my_function(input):
        """ Function using the type."""
        global _api
        if hasattr(input, "_ctx"):
            input = input._ctx[0]

        return _api.my_function(input)

Dependencies