#ffi #code-generation #bindings #python

interoptopus_backend_cpython

Generates CPython bindings

6 releases

new 0.13.6 Oct 26, 2021
0.13.5 Oct 20, 2021

#7 in #csharp

Download history 44/week @ 2021-10-05 66/week @ 2021-10-12 54/week @ 2021-10-19

59 downloads per month

MIT license

160KB
2.5K SLoC

Generates CPython bindings for Interoptopus.

Usage

Assuming you have written a crate containing your FFI logic called example_library_ffi and want to generate CPython 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 = "..."

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::{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 __future__ import annotations
import ctypes
import typing

T = typing.TypeVar("T")
c_lib = None

def init_lib(path):
    """Initializes the native library. Must be called at least once before anything else."""
    global c_lib
    c_lib = ctypes.cdll.LoadLibrary(path)
    c_lib.my_function.argtypes = [Vec2]
    c_lib.my_function.restype = Vec2


def my_function(input: Vec2) -> Vec2:
    return c_lib.my_function(input)


TRUE = ctypes.c_uint8(1)
FALSE = ctypes.c_uint8(0)


class Vec2(ctypes.Structure):
    # These fields represent the underlying C data layout
    _fields_ = [
        ("x", ctypes.c_float),
        ("y", ctypes.c_float),
    ]

    def __init__(self, x: float = None, y: float = None):
        if x is not None:
            self.x = x
        if y is not None:
            self.y = y

    @property
    def x(self) -> float:
        return ctypes.Structure.__get__(self, "x")

    @x.setter
    def x(self, value: float):
        return ctypes.Structure.__set__(self, "x", value)

    @property
    def y(self) -> float:
        return ctypes.Structure.__get__(self, "y")

    @y.setter
    def y(self, value: float):
        return ctypes.Structure.__set__(self, "y", value)

Dependencies

xa