Integrating Automatically Generated FFI Bindings

There are a number of tools to automatically generate bindings between Rust and different foreign languages.

  1. bindgen
  2. cbindgen
  3. cxx

bindgen

bindgen is a tool to automatically generate Rust bindings from C headers. As such, integrating bindgen via a build-script works well and their doesn't seem to be a need to create CMake rules for generating the bindings.

cbindgen integration

⚠️⚠️⚠️ EXPERIMENTAL ⚠️⚠️⚠️

cbindgen is a tool that generates C/C++ headers from Rust code. When compiling C/C++ code that #includes such generated headers the buildsystem must be aware of the dependencies. Generating the headers via a build-script is possible, but Corrosion offers no guidance here.

Instead, Corrosion offers an experimental function to add CMake rules using cbindgen to generate the headers. This is not available on a stable released version yet, and the details are subject to change.

A helper function which uses cbindgen to generate C/C++ bindings for a Rust crate. If cbindgen is not in PATH the helper function will automatically try to download cbindgen and place the built binary into CMAKE_BINARY_DIR. The binary is shared between multiple invocations of this function.

The function comes with two different signatures. It's recommended to use the TARGET based signature when possible.

Auto mode (With a Rust target imported by corrosion)

corrosion_experimental_cbindgen(
        TARGET <imported_target_name>
        HEADER_NAME <output_header_name>
        [CBINDGEN_VERSION <version>]
        [FLAGS <flag1> ... <flagN>]
)

Auto-mode specific Arguments

  • TARGET: The name of an imported Rust library target, for which bindings should be generated. If the target is not imported by Corrosion, because the crate only produces an rlib, you can instead use the second signature and manually pass MANIFEST_DIRECTORY, CARGO_PACKAGE and BINDINGS_TARGET

Manual mode (Without a Rust target imported by corrosion)

corrosion_experimental_cbindgen(
        MANIFEST_DIRECTORY <package_manifest_directory>
        CARGO_PACKAGE <package_name>
        BINDINGS_TARGET <cmake_library>
        [TARGET_TRIPLE <rust_target_triple>]
        HEADER_NAME <output_header_name>
        [CBINDGEN_VERSION <version>]
        [FLAGS <flag1> ... <flagN>]
)

Manual-mode specific Arguments

  • MANIFEST_DIRECTORY: Manual mode only. Directory of the package defining the library crate bindings should be generated for. If you want to avoid specifying MANIFEST_DIRECTORY you could add a staticlib target to your package manifest as a workaround to make corrosion import the crate.

  • CARGO_PACKAGE: Manual mode only. The name of the cargo package that bindings should be generated for. Note: This corresponds to the cbindgen --crate option, which actually wants a package name.

  • BINDINGS_TARGET: Manual mode only. Name of an INTERFACE CMake target that the generated bindings should be attached to. In auto mode, the generated headers will be attached to the imported rust CMake crate, and corrosion will take care of adding the necessary build dependencies. In manual mode, this target likely doesn't exist, so the user needs to specify an INTERFACE CMake target, which the header files should be attached to. The user must create this target themselves and ensure to add any necessary dependencies (e.g. via add_dependencies()) to ensure that consumers of the INTERFACE library are not linked before the Rust library has been built.

  • TARGET_TRIPLE: Manual mode only. Rust target triple (e.g. x86_64-unknown-linux-gnu) that cbindgen should use when generating the bindings. Defaults to target triple that corrosion was confiured to compile for.

Common Arguments

  • HEADER_NAME: The name of the generated header file. This will be the name which you include in your C/C++ code (e.g. #include "myproject/myheader.h" if you specify HEADER_NAME "myproject/myheader.h"`.
  • CBINDGEN_VERSION: Version requirement for cbindgen. Exact semantics to be specified. Currently not implemented.
  • FLAGS: Arbitrary other flags for cbindgen. Run cbindgen --help to see the possible flags.

Current limitations

  • Cbindgens (optional) macro expansion feature internally actually builds the crate / runs the build script. For this to work as expected in all cases, we probably need to set all the same environment variables as when corrosion builds the crate. However the crate is a library, so we would need to figure out which target builds it - and if there are multiple, potentially generate bindings per-target? Alternatively we could add support of setting some environment variables on rlibs, and pulling that information in when building the actual corrosion targets Alternatively we could restrict corrosions support of this feature to actual imported staticlib/cdylib targets.

cxx integration

⚠️⚠️⚠️ EXPERIMENTAL ⚠️⚠️⚠️

cxx is a tool which generates bindings for C++/Rust interop.

corrosion_add_cxxbridge(cxx_target
        CRATE <imported_target_name>
        REGEN_TARGET <regen_target_name>
        [FILES <file1.rs> <file2.rs>]
)

Adds build-rules to create C++ bindings using the cxx crate.

Arguments:

  • cxxtarget: Name of the C++ library target for the bindings, which corrosion will create.
  • FILES: Input Rust source file containing #[cxx::bridge].
  • CRATE: Name of an imported Rust target. Note: Parameter may be renamed before release
  • REGEN_TARGET: Name of a custom target that will regenerate the cxx bindings without recompiling. Note: Parameter may be renamed before release

Currently missing arguments

The following arguments to cxxbridge currently have no way to be passed by the user:

  • --cfg
  • --cxx-impl-annotations
  • --include

The created rules approximately do the following:

  • Check which version of cxx the Rust crate specified by the CRATE argument depends on.
  • Check if the exact same version of cxxbridge-cmd is installed (available in PATH)
  • If not, create a rule to build the exact same version of cxxbridge-cmd.
  • Create rules to run cxxbridge and generate
    • The rust/cxx.h header
    • A header and source file for each of the files specified in FILES
  • The generated sources (and header include directories) are added to the cxxtarget CMake library target.

Limitations

We currently require the CRATE argument to be a target imported by Corrosion, however, Corrosion does not import rlib only libraries. As a workaround users can add staticlib to their list of crate kinds. In the future this may be solved more properly, by either adding an option to also import Rlib targets (without build rules) or by adding a MANIFEST_PATH argument to this function, specifying where the crate is.

Contributing

Specifically some more realistic test / demo projects and feedback about limitations would be welcome.