Description: Using bindgen and Rust to call C/C++ code
Authored: 2021-10-08;
Permalink: https://adamflott.com/programming/rust/ffi-example/
tags :
ffi;
rust;
categories :
programming;
I've used a number of FFIs (Foreign_function_interface ) over the years, but was very impressed with how quickly I got something working with Rust and bindgen .
At work, we have our own SQL-like database, the wire protocol is complicated enough that it's not worth building a native version. Instead there is a C++/C library that handles everything. What makes this a tricky integration is the fact that the database library is:
I have some prior experience with using a binding against this library. I tried (and barely passed) using Go for a project, but ran into all sorts of random segfaulting due to the Go runtime. It may be better as that was around 2016.
First start with a Cargo.toml
[package]
name = "query-ffi-example"
version = "0.1.0"
edition = "2018"
build = "build.rs"
[dependencies]
libc = "0.2"
[build-dependencies]
cc = { version = "1.0", features = ["parallel"] }
pkg-config = "0.3"
In order to link against we need a build.rs
file. Some of the libs are static archives (.a
) and some a dynamic shared objects (.so
).
// build.rs
fn main() {
println!("cargo:rustc-link-lib=static=liba");
println!("cargo:rustc-link-lib=static=libb");
println!("cargo:rustc-link-lib=crypto");
println!("cargo:rustc-link-lib=static=eventloop");
println!("cargo:rustc-link-lib=static=query");
println!("cargo:rustc-link-search=/some/dir/query2lib");
println!("cargo:rustc-flags=-l dylib=stdc++");
}
dylib=stdc++
link flag to due some of the libraries being in C++ and dealing with name manglingLD_LIBRARY_PATH
later to load them) Generate the bindings via bindgen
.
$ bindgen include/query/query.h -o bindings.rs -- -I../common/include/
This creates a large file we don't have to edit:
$ wc -l src/bindings.rs
5980 src/bindings.rs
Our lib.rs
can include bindings.rs
for convenience of building
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!("./bindings.rs");
Our main.rs
looks like:
use std::ffi::{CStr, CString};
use std::ptr;
use std::{thread, time};
use query2lib::*;
extern "C" fn callback(qt: *mut query_table, _user_arg: *mut libc::c_void) {
println!("I'm called from C with value");
unsafe {
let c_str = query_dump_table_as_string(qt, query_dump_format_DUMP_DEFAULT);
let rust_str = CStr::from_ptr(c_str).to_string_lossy().into_owned();
println!("{}", rust_str);
}
}
fn main() {
let id = CString::new("rust-test").unwrap();
let hostname = CString::new("db-hostname").unwrap();
unsafe {
let conn = query_init(id.as_ptr(), hostname.as_ptr(), query_init_flag_INIT_NONE as i32);
let sql = CString::new("SELECT current_timestamp;").unwrap();
let fcb: query_callback = Some(callback);
let p: *const i32 = ptr::null();
let query_result = query_run(conn, sql.as_ptr(), fcb, p as *mut libc::c_void);
println!("query_run result: {}", query_result);
let ten_millis = time::Duration::from_millis(10000);
thread::sleep(ten_millis);
query_exit(conn);
}
}
unsafe
, but that can be hidden behind a pure Rust interfacelibc
things right, but rustc
hasn't complainedAnd when running we get:
$ export LD_LIBRARY_PATH=`pwd`
$ ./target/debug/query-ffi-example
query_run result: 0
I'm called from C with value
CURRENT_TIMESTAMP
------------------------
Fri Oct 08 22:34:20 2021
1 rows selected
All that in just a few hours!