Store reference to struct instance as `void *` [C FFI]


#1

Hi,

I’m trying to write a Rusty wrapper for libsound.io but I’m hopelessly stuck at wrapping the callback usage. The easiest way would be to expose the raw callback function type. Doing this would force the library user to define unsafe callbacks like extern "C" my_callback(stream: *mut RawOutStream, frame_count_max: c_int, ...) and register them, which is clearly bad practice.
This means I need some kind of wrapper for the C callback that translates the C types to their Rust equivalent and calls the user-defined callback. Luckily, the structs in libsoundio provide a void *userdata field where I could store a (boxed) reference to the instance of OutStream (see the new function below).
Converting from *mut c_void to Box::<&OutStream> seems to work, but the callback wrapper crashes as soon as I try to access some function from the struct instance.

pub struct OutStream {
    stream: *mut structs::RawOutStream,
    write_cb: Option<Box<Fn(&OutStream, i32, i32)>>,
}
impl OutStream {
    fn new(raw_stream: *mut structs::RawOutStream) -> Self {
        let out_stream = OutStream {
            stream: unsafe {
                (*raw_stream).write_callback = Some(OutStream::wrapper);
                raw_stream
            },
            write_cb: None,
        };
        unsafe {
            (*out_stream.stream).userdata = Box::into_raw(Box::new(&out_stream)) as *mut c_void;
        }
        out_stream
    }

    extern "C" fn wrapper(stream: *mut structs::RawOutStream,
                          frame_count_min: c_int,
                          frame_count_max: c_int) {
        unsafe {
            let ud_ptr = (*stream).userdata;
            if !ud_ptr.is_null() {
                let out_stream: Box<&OutStream> = Box::from_raw(ud_ptr) as Box<&OutStream>;
                println!("name: {}", out_stream.to_string()); // crashes with Signal 11 (ToString trait is implemented, but not shown here)
            } else {
                panic!("Userdata pointer is null!");
            }
        }
    }

    // ...

    pub fn set_callback(&mut self, callback: Box<Fn(&OutStream, i32, i32)>) {
        self.write_cb = Some(callback);
    }
}

Wrapper for C struct SoundIoOutStream:

  • userdata should contain the reference to the wrapper struct OutStream (see above)
  • write_callback stores the reference to the callback wrapper defined in OutStream which tries to convert the void pointer userdata back to a Box<&OutStream> and calls the user defined callback
#[derive(Debug)]
#[repr(C)]
pub struct RawOutStream {
    // ...
    pub userdata: *mut c_void,
    pub write_callback: Option<extern "C" fn(*mut OutStream, c_int, c_int)>,
    // ...
}

#2

I’ve created a little playpen to demonstrate the problem and make my intentions a bit clearer.


#3

I do something similar

https://github.com/emoon/ProDBG/blob/rust/api/rust/prodbg/src/view.rs

Example usage

https://github.com/emoon/ProDBG/blob/rust/src/plugins/bitmap_memory/src/lib.rs


#4

The immediate cause of the crash you’re getting is that you’re not creating an OutStream on the heap; you’re creating an &OutStream on the heap, which is a pointer to stack memory that is freed as soon as OutStream::new returns. If you created a Box<OutStream> instead, you might have better luck.


#5

@sorear
The problem with Box::new(out_stream) is, that out_stream will be moved into the Box and I can’t return out_stream.