Unexpected behavior of c_void pointer in Rust FFI to C

97 views Asked by At

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_void pointer (which carries over as a void* pointer to C, I assume) to the struct.
  • a function which receives a *mut c_void pointer to my rust struct, dereferences it and returns the float of the struct.

I followed these references

Working with c_void in an FFI

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.

0

There are 0 answers