fiffi 0.1.1

Rust bindings for libffi, alternative to libffi-rs
docs.rs failed to build fiffi-0.1.1
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: fiffi-0.1.2

fiffi

Crates.io Docs Apache 2.0 licensed MIT licensed Build Status

fiffi ([fi`f:i]) provides Rust bindings for libffi. libffi-rs already provides Rust bindings for libffi, but fiffi aims to provide more ergonomic bindings that makes it easier to avoid safety issues when using functionality provided by libffi.

The crate uses libffi-sys-rs to build and link the underlying libffi library. fiffi is no_std but requires alloc.

Use Cases

fiffi is built around three main use cases:

  • Calling foreign functions whose signatures are not fully known at compile time with Function.
  • Creating function pointers to Rust closures with Closure.
  • Creating function pointers with arbitrary signatures chosen at run time backed by Rust callbacks with DynamicClosure.

Examples

Add fiffi to your Cargo.toml:

[dependencies]
fiffi = "0.1"

Calling an FFI function with Function

use fiffi::function::{Function, arg, ret};
use fiffi::types::Type;

// Typically when using fiffi, the function's signature is not known at compile-time. If you know
// the function signature at compile-time, it is probably preferable to call the function pointer
// directly instead of using an external library for it.
extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}

let function = Function::new(
    fiffi::fn_ptrize!(add),
    &[Type::I32, Type::I32],
    Some(&Type::I32),
);

let a = 10i32;
let b = 32i32;
let mut result = 0i32;

// SAFETY: `function` was built from `add` with matching argument and return
// types, and `result` is valid storage for the return value.
unsafe {
    function.call([arg(&a), arg(&b)], ret(&mut result));
}

assert_eq!(result, 42);

Converting a Rust closure to a function pointer with Closure

use fiffi::closure::Closure;

let offset = 5i32;
let add_offset = Closure::new(move |value: i32| value + offset);

// `add_offset.as_fn_ptr()` can now be used to retrieve an `extern "C"` function pointer to call the
// closure.

// SAFETY: `add_offset` accepts one `i32` argument, returns `i32`, and remains alive while the
// function pointer is used.
let add_offset_fn = unsafe { add_offset.as_fn_ptr().into_fn::<extern "C" fn(i32) -> i32>() };

assert_eq!(add_offset_fn(1), 6);

Creating a function pointer with a function signature defined at run time with DynamicClosure

use core::mem::MaybeUninit;

use fiffi::closure::dynamic::DynamicClosureCall;
use fiffi::closure::DynamicClosure;
use fiffi::types::Type;

fn add_offset_callback(mut call: DynamicClosureCall<i32>) {
    let arg = call
        .args()
        .get(0)
        .expect("This callback must be used with exactly one argument.");

    if arg.ty() != &Type::I32 {
        // **NOTE** This will abort instead of unwinding regardless of the crate's setting
        // because Rust cannot unwind past libffi's `extern "C"` functions.
        panic!("This callback can only be used with an `i32` argument.");
    }

    let mut value = MaybeUninit::<i32>::uninit();

    // SAFETY: The argument's type is `i32`. The `copy_to` fully initializes `value`.
    let result = unsafe {
        arg.copy_to(&mut value);
        value.assume_init()
    } + call.context();

    let ret = call
        .ret()
        .expect("This callback expects a return value.");

    if ret.ty() != &Type::I32 {
        // **NOTE** This will abort instead of unwinding regardless of the crate's setting
        // because Rust cannot unwind past libffi's `extern "C"` functions.
        panic!("This callback can only be used with an `i32` return value.");
    }

    // SAFETY: The return value's type is `i32`.
    unsafe {
        ret.write(result);
    }
}

let add_offset = DynamicClosure::new(
    add_offset_callback,
    &[Type::I32],
    Some(&Type::I32),
    5,
);

// SAFETY: `add_offset` represents an `extern "C"` function that accepts one `i32` and returns an
// `i32`. `add_offset` remains alive while it is called.
let add_offset_fn = unsafe {
    add_offset
        .as_fn_ptr()
        .into_fn::<extern "C" fn(i32) -> i32>()
};

assert_eq!(add_offset_fn(37), 42);

Features

  • closure: includes Closure and DynamicClosure function-pointer support. This feature is enabled by default.
  • check_only: forwards libffi-sys's check_only feature to speed up cargo check by not compiling libffi.
  • system: forwards libffi-sys's system feature to dynamically link to a system libffi instead of compiling a static library. Note that some functionality may require libffi 3.5.0.

libffi-rs or fiffi?

See libffi-rs-2-fiffi.md for a comparison of how two simple code snippets using libffi-rs would be written using fiffi.

Reasons to prefer libffi-rs

  • Mature project with more than 1 maintainer
  • Used by large projects such as miri and deno
  • Prioritizes stability and support for older Rust versions
  • Several abstraction levels (low, middle, high) for different needs

Reasons to prefer fiffi

  • Smaller API with one way to do something rather than different abstraction levels
  • Designed to not allow invalid function signatures or ABIs, which may cause libffi-rs to return an error or panic

Supported targets

The targets below are tested in fiffi's CI tests. Additional targets may work, but are not tested in CI; run the full test suite before using fiffi on another platform to ensure that everything works as intended.

Development currently occurs mainly on x86_64 Linux and Windows.

  • aarch64-apple-darwin
  • aarch64-pc-windows-gnullvm
  • aarch64-pc-windows-msvc
  • aarch64-unknown-linux-gnu
  • armv7-unknown-linux-gnueabihf
  • i686-pc-windows-gnu
  • i686-pc-windows-msvc
  • i686-unknown-linux-gnu
  • loongarch64-unknown-linux-gnu
  • powerpc-unknown-linux-gnu
  • powerpc64-unknown-linux-gnu
  • powerpc64le-unknown-linux-gnu
  • riscv64gc-unknown-linux-gnu
  • s390x-unknown-linux-gnu
  • sparc64-unknown-linux-gnu
  • x86_64-apple-darwin
  • x86_64-pc-windows-gnu
  • x86_64-pc-windows-gnullvm
  • x86_64-pc-windows-msvc
  • x86_64-unknown-linux-gnu
  • x86_64-unknown-linux-musl