I'm new to Rust and try to implement a foreign function interface which enables C code to interact with Rust structs. The functions save results within the structs for later use. I want to compile my Rust code into a C-dll.
As a simple example, I tried to implement
- a simple struct which holds a single float.
- a function to initialize the struct with a float and return a
*mut c_voidpointer (which carries over as avoid*pointer to C, I assume) to the struct. - a function which receives a
*mut c_voidpointer to my rust struct, dereferences it and returns the float of the struct.
I followed these references
https://mobiarch.wordpress.com/2023/01/06/rust-struct-to-c-pointer-and-back/
Here's my code so far:
use core::ffi::c_void;
#[repr(C)]
pub struct number_container{
contained_number:f64,
}
#[no_mangle]
pub extern "C" fn init_number_container(number:f64) -> *mut c_void {
let mut num_cont:number_container = number_container {contained_number:number};
let ptr: *mut number_container = &mut num_cont;
let voidptr = ptr as *mut c_void;
voidptr
}
#[no_mangle]
pub extern "C" fn get_number_from_container(num_cont: *mut c_void) -> f64{
// UNCOMMENT THIS LINE --> num_cont2.contained_number becomes 0
// println!("{}", format!("voidptr-adress {:p}", num_cont) );
let num_cont2: &mut number_container = unsafe { &mut *(num_cont as *mut number_container) };
return num_cont2.contained_number
}
fn main() {
println!("contained number: {}", get_number_from_container(init_number_container(12.83454)) );
}
When I compile and run the code I obtain the expected result:
contained number: 12.83454
However, if I uncomment the println!(...) line, which prints the memory address the c_void pointer is pointing to, I get the result:
contained number: 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001134413904036
To my surprise, printing the memory address apparently resetted the value contained in the rust struct. I guess I've made a mistake during the referencing or dereferencing process. Does anybody have an idea what's wrong here? bonus question: How to correctly implement this in the context of a Rust FFI to C?
EDIT: Ok, I think I got it done correctly now. Here's my improved example using raw pointers and a function to free the memory:
#[repr(C)]
pub struct number_container{
contained_number:f64,
}
#[no_mangle]
pub extern "C" fn init_number_container(number:f64) -> *mut number_container {
Box::into_raw(Box::new(number_container { contained_number: (number) }))
}
#[no_mangle]
pub extern "C" fn free_number_container(num_cont:*mut number_container)
{
unsafe
{
assert!(!num_cont.is_null());
drop(Box::from_raw(num_cont));
}
}
#[no_mangle]
pub extern "C" fn get_number_from_container(num_cont: *mut number_container) -> f64{
return unsafe { (*num_cont).contained_number }
}
fn main() {
let num_cont: *mut number_container = init_number_container2(12.83454);
println!("{}", format!("pointer-adress {:p}", &num_cont) );
println!("contained number: {}", get_number_from_container(num_cont) );
free_number_container(num_cont);
}
For the sake of completeness, my C-header file looks like this:
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
void *init_number_container(double number);
double get_number_from_container(void *num_cont);
void free_number_container(void* num_cont);
If you create the header with cbindgen, it will use number_container* pointers instead of void* pointers. As @Chayim Friedman pointed out, one can safely change the type of the pointers to void* as Rust and C's ABI are compatible.