How to Call C or C++ Functions from Rust Code

How to wrap existing C functions with Rust or how to call C functions from Rust?

If you're trying to call C code from Rust, you need to create FFI bindings as some people have mentioned in the comments.

However, it is usually best to do this via rust-bindgen, which automatically generates the bindings and includes tests to make sure that the results are properly sized, aligned, etc. It is very easy to create a type with an incorrect size, which will give you no warning at compile-time and can cause undefined behavior unless you generate tests for them with bindgen.

How to call a Rust function in C?

If you have this C code compiled to a library named lib.dll, exporting a single function which accepts a pointer to a function which takes no arguments and returns nothing:

__declspec(dllexport) void foo(void (*callback)()) {
callback();
}

Then this Rust code will send the function callback to that C function. Upon execution, it prints the line "callback()":

extern "C" fn callback() -> () {
println!("callback()");
}

fn main() {
println!("main()");
call_dynamic();
}

fn call_dynamic() -> Result<u32, Box<dyn std::error::Error>> {
unsafe {
let lib = libloading::Library::new("lib.dll")?;
let foo: libloading::Symbol<extern "C" fn(extern "C" fn()) -> u32> = lib.get(b"foo")?;
Ok(foo(callback))
}
}

You should see this in your console:

main()
callback()

I am on Windows and compiled the C code with this command:

gcc --shared lib.c -o lib.dll

The Rust code was run with this:

cargo run

Is it possible to receive a pointer to C function in Rust and call it back?

You can call freely, but unsafely, from C to Rust and from Rust to C.

From Rust to C: write a extern "C" block with the prototypes of the C functions.

From C to Rust: write the Rust function with a #[no_mangle] extern "C" prefix. You must ensure that any value received or returned is FFI friendly: basic types or repr(C) types or raw pointers to those and a few others.

Also you will need the equivalent C declarations to those of Rust. for that you can use the types in std::os::raw, std::ffi and the external crate libc.

Particularly, the answer to your questions about function pointers, a type such as this in C:

typedef int (*callback)(uint8_t *data, size_t len);

is translated into Rust as:

type Callback = unsafe extern "C" fn(data: *mut u8, len: usize) -> c_int;

This type Callback is FFI friendly, so you can store it, pass between Rust and C, and call it whenever you want. And of course you can use it as argument or return type in other FFI declarations:

int do_the_thing(uint8_t*data, size_t len, callback cb);
#[no_mangle]
pub extern "C" fn do_the_thing(
data: *const u8, len: usize,
cb: Option<Callback>) -> c_int)
{
//...
}

Remember that in Rust pointer functions are non-nullable, so if you want C to be able to pass NULL you use Option<fn>.

For more details about FFI in Rust you can read this chapter of the Rustonomicon.

How can I export a function written in Rust to C code?

The problem is coming from the forward declaration from bindgen. Rust unlike C and C++ does not have forward declaration. So remove this:

extern "C" {
pub fn ErrorHandler(Error: StatusType);
}

and keep this:

#[no_mangle]
pub extern "C" fn ErrorHandler(Error: StatusType) {
/* do something */
}

Problem with calling C++ function that receive command line arguments from Rust

The problem is in this code:

let args = std::env::args()
.map(|arg| CString::new(arg).unwrap())
.collect::<Vec<CString>>();
// ...
let c_args_ptr = args.as_ptr() as *const c_char;

That creates a vector of CString objects, which you then proceed to cast into an array of pointers. But a CString consists of two word-sized values, a pointer and a length, and cannot be reinterpreted as a single pointer. To get an actual array of pointers which print_args() expects, you need to collect them into a separate vector:

let args = std::env::args()
.map(|arg| CString::new(arg).unwrap())
.collect::<Vec<CString>>();
let arg_ptrs: Vec<*const c_char> = args.iter().map(|s| s.as_ptr()).collect();
let args_len: c_int = args.len() as c_int;
unsafe { print_args(args_len, arg_ptrs.as_ptr()) };

Note that you'll need to declare print_args as taking pointer to pointer, as it does in C++ (const char *argv[] is just sugar for const char **argv):

#[link(name = "library", kind = "static")]
extern "C" {
pub fn print_args(args: c_int, argsv: *const *const c_char);
}

Is it possible to call a function from static library that built in Rust using C?

Setting up a project

Create a new cargo library as usual:

$ cargo new my_rust_library --lib

You can then configure cargo to emit a systems library instead of its regular rust target:

[lib]
crate-type = ["cdylib"] # dynamic lib
# crate-type = ["staticlib"] # static lib

Building a C API

You can mark a function as extern "C" to tell the compiler to use the C ABI. You also need to add the #[no_mangle] annotation to tell the compiler not to mangle the name of this function:

#[no_mangle]
pub extern "C" fn message() {
println!("Hello C!")
}

Linking

You can build your crate as usual:

$ cargo build --release

cargo will create a my_lib.so or my_lib.a file in the target/release directory. This library can simply be linked by your build system. However, calling a Rust function from C requires a header file to declare the function signatures.

Every extern fn needs to have a corresponding header function.

#[no_mangle]
pub extern "C" fn message() {}

would become:

void message(void);

You can automate this process with cbindgen, a crate which analyzes your Rust code and generates the corresponding C/C++ headers. You can install it with cargo:

$ cargo install cbindgen

Create an empty config file:

$ touch cbindgen.toml

And generate the header file:

$ cbindgen --config cbindgen.toml --crate my_rust_library --output my_header.h --lang c

Now you can simply include the header and call you Rust functions from C!

#include "my_header.h"
message()

How do I pass arguments by reference when calling Rust from C++?

#[no_mangle]
// See note below
pub extern "C" fn add_one(x: &mut i32) {
*x += 1;
}
#include <iostream>
#include <cstdint>

extern "C" {

void add_one(int32_t *x);

} // extern "C"

using namespace std;

int main() {
int32_t x = 14;
cout << x << endl;
add_one(&x);
cout << x << endl;
cout << x << endl;
}

By using &mut in the function argument, we are requiring that the caller provides a valid reference. Among other things, that requires that:

  • It not be NULL
  • It be properly aligned
  • It doesn't alias any other value.

It's up to the caller of the function to ensure these conditions, otherwise it will cause undefined behavior.

See also:

  • The Rust FFI Omnibus
  • Passing a Rust variable to a C function that expects to be able to modify it
  • How do I pass a reference to mutable data in Rust?
  • What's the difference between placing "mut" before a variable name and after the ":"?


Related Topics



Leave a reply



Submit