Rich
Rich

Reputation: 976

Creating a rust shared library that returns a struct of function pointers to C main program

I'm trying to make a Rust binding to nbdkit without much luck. I need to make a .so file, which is easy. The .so file must have a public function called plugin_init, also easy. However this function must return a pointer to a C-compatible struct containing a mix of strings and function pointers (that the C main program will later call).

The API is: https://github.com/libguestfs/nbdkit/blob/409ce4c9238a84ede6688423b20d5f706067834b/include/nbdkit-plugin.h#L53

I came up with:

#[repr(C)]
pub struct NBDKitPlugin {
    _struct_size: uint64_t,
    _api_version: c_int,
    _thread_model: c_int,

    name: *const c_char,
    longname: Option<*const c_char>,
    version: Option<*const c_char>,
    description: Option<*const c_char>,

    load: Option<extern fn ()>,
    unload: Option<extern fn ()>,

    config: Option<extern fn ()>, // XXX
    config_complete: Option<extern fn () -> c_int>,
    config_help: Option<*const c_char>,

    open: extern fn (c_int) -> *mut c_void,
    close: Option<extern fn (*mut c_void)>,
}

and a plugin_init function:

extern fn hello_load () {
    println! ("hello this is the load method");
}

struct MyHandle {
}

extern fn hello_open (readonly: c_int) -> *mut c_void {
    println! ("hello, this is the open method");
    let mut h = MyHandle {};
    let vp: *mut c_void = &mut h as *mut _ as *mut c_void;
    return vp;
}

#[no_mangle]
pub extern fn plugin_init () -> *const NBDKitPlugin {
    println! ("hello from the plugin");

    let plugin = Box::new (NBDKitPlugin {
        _struct_size: mem::size_of::<NBDKitPlugin>() as uint64_t,
        _api_version: 2,
        _thread_model: 3,
        name: CString::new("hello").unwrap().into_raw(),
        longname: None,
        version: None,
        description: None,
        load: Some (hello_load),
        unload: None,
        config: None,
        config_complete: None,
        config_help: Some (CString::new("my config_help here").unwrap().into_raw()),
        open: hello_open,
        close: None,
    });

    return Box::into_raw(plugin);
}

Apart from leaking memory, this partially works. The integers and strings are seen from C OK. However the function pointers don't work at all. They are completely bogus and seem to occupy more space than a raw pointer so I suppose that I'm exposing a "fat" Rust pointer.

There seems very little documentation on this topic that I can find. Help.

Upvotes: 0

Views: 530

Answers (1)

Francis Gagn&#233;
Francis Gagn&#233;

Reputation: 65935

You've probably learned at some point that a reference (&T) wrapped in an Option (Option<&T>) is optimized such that None is encoded as all zeroes, which is not valid for a reference, and Option<&T> has the same size as &T.

However, all zeroes is a valid bit pattern for a raw pointer (*const T or *mut T): it represents the null pointer. As such, wrapping those in an Option is no different from wrapping, say, an i32 in an Option: the Option type is larger so that it can store a discriminant.

To fix your struct definition, you must not use Option to define longname, version and description.

Upvotes: 1

Related Questions