The Rust FFI
Rust offers a powerful FFI interface. We can easily use
extern "C" fn to
create functions that can be called from C. A simple example:
#[no_mangle] ensures that the compiler does no additional name-mangling, allowing us to call this function from C, while
extern "C" ensures that the function gets exported. There are a few other primitives that help writing FFI interfaces for C, which
can be found in the documentation.
Generating header files
C/C++ code uses header files to define interfaces. In order to provide an FFI interface for C, we must provide a suitable headerfile
cbindgen to the rescue! Cbindgen can create a header file for you, based on your
extern "C" interfaces.
There are two ways of using cbindgen. I personally prefer to always generate a header file during a build, so we will use a build.rs to call
cbindgen during a build.
Cargo.toml add our build-dependency:
We nwo generate a new header file during build. Note how we are configuring cbindgen to use a configuration file.
cbindgen comes with a good set of defaults. However in our case, those were not alway sufficient. For once, cbindgen defaults to build C++ bindings, while we want C bindings. In addition it tries to parse dependent libraries for types, in case they are needed. So far I don’t want or need this, as I am trying to build a very clear abstraction that does not accidentally leak an internal type in the header files. So we will disable it.
cbindgen.toml now looks like:
Let’s do a simple run:
$ cargo build
We now have a proper C library with a header file. But how can we use it?
At this point we need to generate a library that contians our Rust code in an ingestible format for our C program. Rust builds rlibs, a binary format of a library that is targeting other rust code. C/C++ linkers do not support rlibs, so we need to go and build a dynmic or a static library. In my case I want a dynamic library.
We have to modify our
Cargo.toml again. We are adding the configuration
crate-type indication that we want to build
a dynamic library for C by using the argument cdylib. On MacOS this will produce a
.dynlib, on Linux an
on Windows a
Building now results in a
xilib.dll in target/debug. Now off to the final step, linking!
The C program
Let’s create a siple C program,
In order to link it, we must:
- Provide the pathg to xilib.h which was generated by cbindgen
- Provide the definition of the library interface in form of a
For 2., cargo alreayd produced us an additional
xilib.dll.lib file. So let’s link it all together:
$ cl.exe /Iheaders/ target/debug/xilib.dll.lib main.c
We now can start our newly created
main.exe. We must ensure that xilib.dll is in the same directory, and voila:
$ main.exe hello world