C-Bindings with Rust
Enable Javascript to display Table of Contents.
The sources of the example project are available at github.com.
Preparation
Install clang with apt install llvm-dev libclang-dev clang
(clang version >= 3.9 is recommended)
Project Setup
Create a new directory and place a Cargo.toml
with the following content (see doc.rust-lang.org for more details):
[workspace]
members = [
"example",
"library"
]
Then create an application sub-project called example
and a library
sub-project called library:
$ cargo new example
Created binary (application) `example` package
$ cargo new library --lib
Created library `library` package
$
Build the project to verify that it's setup correctly:
$ cargo build
Compiling library v0.1.0 (/home/charly/Projects/RUST/rust-cbinding-template/library)
Compiling example v0.1.0 (/home/charly/Projects/RUST/rust-cbinding-template/example)
Finished dev [unoptimized + debuginfo] target(s) in 0.28s
$
Library with C-Binding
In a first step, add a bindgen
dependency to Cargo.toml
. As version, use the latest version from crates.io:
[build-dependencies]
bindgen = "0.59"
In a next step, we create a C header file, which includes all headers we want to provide to RAST. In this file (bindings.h
) we also may add replacement types and blocklisting:
#ifndef BINDINGS_H
#define BINDINGS_H
#include "libfoo/foo.h"
#endif // BINDINGS_H
In the next step, we create a build.rs
. It's a RUST program which output is parsed to handle e.g. the compilation and FFI generation of our C library.
For compiling the library and creating a
bingen tutorial
extern crate bindgen;
use std::env;
use std::path::PathBuf;
use std::process::Command;
fn main() {
// Compile libfoo with make
Command::new("make")
.arg("-C")
.arg("libfoo")
.arg("CROSS_COMPILE=")
.output()
.expect("failed to execute process");
// Add dependency to libfoo
println!("cargo:rustc-link-lib=foo");
println!("cargo:rerun-if-changed=bindings.h");
let bindings = bindgen::Builder::default()
.header("bindings.h")
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
The header file of our C code looks like this:
#ifndef LIBFOO_FOO_H
#define LIBFOO_FOO_H
void foo_reset();
int foo_get_value();
void foo_set_value(int value);
#endif // LIBFOO_FOO_H
When running cargo build
a file called bindings.rs
is generated in the target directory:
/* automatically generated by rust-bindgen 0.59.1 */
extern "C" {
pub fn foo_reset();
}
extern "C" {
pub fn foo_get_value() -> ::std::os::raw::c_int;
}
extern "C" {
pub fn foo_set_value(value: ::std::os::raw::c_int);
}
This generated file can be included in your lib.rs
and thus will be the public API for your crate:
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
Source: RUST book and bindgen user's manual
Example Application
To be able to use our library crate, we have to add it as dependency in the example/Cargo.toml
:
[dependencies]
raw_foo = { path = "../library" }
In the application itself, we can either use use raw_foo::*;
to import all methods at once, or call them with the crate prefix:
fn main() {
unsafe {
println!("initial raw value: {}", raw_foo::foo_get_value());
raw_foo::foo_set_value(13);
println!("raw value after setting 13: {}", raw_foo::foo_get_value());
raw_foo::foo_reset();
println!("raw value after reset: {}", raw_foo::foo_get_value());
}
}