Help with using C plugin API from Rust

Hi,

I'm working on getting a plugin API to work nice with Rust and I have some questions on what approach to how to implement it in a clean fashion.

The API looks like this

struct Reader {
	void* priv_data;
	uint8_t (*readData)(void* data);
};

struct Writer {
	void* priv_data;
	void (*writeData)(void* data, uint8_t t);
};

typedef struct BackendPlugin
{
	const char* name;
	void* (*createInstance)();
	void (*destroyInstance)(void* userData);
	void (*update)(void* userData, Reader* inEvents, Writer* outEvents);
} BackendPlugin;

Each plugin is responsible to implement these functions and the first one that gets called from the outside is createInstance() It's expected that the plugin allocates some data for internal state that gets later on passed to the other functions.

A simple plugin implementation can look like this

struct Plugin1Data { int tempData; };

static void* create_instance1() { return (void*)malloc(sizeof(Plugin1Data)); }
static void destroy_instance1(void* data) { free(data); }
static void update_1(void* userData, Reader* reader, Writer* writer) {
	Plugin1Data* pluginData = (Plugin1Data*)userData;
	// do something here
}

static BackendPlugin myPlugin1 = { "plugin1", 
    create_instance1, 
    destroy_instance1, 
    update_1 };

// Then an array is being exported like that they plugin loader will handle.

// exported 
BackendPlugin* backend_plugins[] = {
	&myPlugin1,
	// more plugins,
	0, // end marker
};

So what I would like the Rust version to be is to handle the above a bit behind the back and provies a safe interface for the code that is being sent in (Reader and Writer for example) this isn't that hard and I can do something like this to wrap it

#[repr(C)]
pub struct CReader {
    priv_data: *mut ::libc::c_void,
    read_data: extern fn(data: *mut ::libc::c_void),
}

pub struct Reader {
    c_reader_api: *mut CReader,
}

impl Reader {
    pub fn read_data(&self) {
        unsafe {
            ((*self.c_reader_api).read_data)((*self.c_reader_api).private_data)
        }
    }
}

So the way I would like the rust code to look is something like this

struct MyBackend {
    some_data: i32,
}

pub trait Backend {
    fn update(&mut self, reader: &Reader, writer: &mut Writer);
}

impl Backend for MyBackend {
    fn update(&mut self, reader: &Reader, writer: &mut Writer)
    {
        reader.read_data();
        writer.write_data(0);
        self.some_data = 0;
    }
}

backend_plugins ... // how should this be constructed?

Also I will need some "trampoline function(s)" to for example update

Now this reader can be used something like this (called from C)

#[no_mangle]
pub extern fn update_from_c(user_data: *mut libc::c_void, reader_api: *mut CReader, writer_api: *mut CWriter) {
	let reader = Reader { api_from_c : api };
	let writer = Writer { api_from_c : writer };
	// call the real code Which should something like this (pseudo)
	MyBackend* backend = user_data;
	(*backend.update)(reader, writer);
}

Also this update_from_c is something I would like to hide from the plugin implementor as it's a implementation detail (and each update needs to cast to the correct type). Perhaps a macro can be used for this?

Sorry for a long post. I really looked around for similar uses but couldn't find any and it's hard to search for it.

2 Likes

I will reply to myself here as I try to make some progress on this (and hopefully others will find it interesting also)

// these needs to be generated for each type

#[repr(C)]
pub struct CBackendCallbacks {
    pub create_instance: fn() -> *mut ::libc::c_void, 
    pub destroy_instance: fn(*mut ::libc::c_void), 
    pub update: fn(*mut ::libc::c_void), 
}

fn call_create_instance() -> *mut ::libc::c_void {
    let instance = unsafe { transmute(Box::new(MyBackend::new())) };
    println!("Lets create instance!");
    instance
}

fn call_destroy_instance(ptr: *mut ::libc::c_void) {
    let instance: Box<MyBackend> = unsafe{ transmute(ptr) };
    // implicitly dropped
}

fn call_update_instance(ptr: *mut ::libc::c_void) { 
    let backend: &mut MyBackend = unsafe { &mut *(ptr as *mut MyBackend) };
    backend.update()
}

#[no_mangle]
pub static mut g_backend: CBackendCallbacks = CBackendCallbacks { 
    create_instance: call_create_instance, 
    destroy_instance: call_destroy_instance, 
    update: call_update_instance 
};

So while this isn't really there yet (and only works for one type and one plugin) it's a step in the right direction at least. Next step would be to figure how to make call_create_instance, etc as generics and then use those for the backend setup.

Next step would be to try to figure out if it would be possible to do some macro to hide this part.

I'd try something like this

trait Backend {
    fn new() -> Self;
    fn update(&mut self);
}

fn call_create_instance<T: Backend>() -> *mut c_void {
    println!("Lets create instance!");
    unsafe {
        Box::into_raw(Box::new(T::new())) as *mut c_void
    }
}

fn call_destroy_instance<T>(ptr: *mut c_void) {
    unsafe {
        Box::from_raw<T>(ptr as *mut T);
        // implicitly dropped
    }
}

fn call_update_instance<T: Backend>(ptr: *mut c_void) { 
    unsafe {
        // This is only valid if call_update_instance are guaranteed
        // to always be called sequentially. Otherwise the reference
        // should be immutable.
        let backend = &mut *(ptr as *mut T);
        backend.update()
    }
}

You should call from_raw, into_raw instead of transmuting the Box.

You could also use dynamic dispatch in the update and destroy functions:


fn call_create_instance<T: Backend>() -> *mut c_void {
    println!("Lets create instance!");
    unsafe {
        let backend: Box<Backend> = Box::new(T::new());
        // put the trait object in another box to get a thin pointer
        Box::into_raw(Box::new(backend)) as *mut c_void
    }
}

fn call_destroy_instance(ptr: *mut c_void) {
    unsafe {
        Box::from_raw(ptr as *mut Box<Backend>);
        // implicitly dropped
    }
}

fn call_update_instance(ptr: *mut c_void) { 
    unsafe {
        // This is only valid if call_update_instance are guaranteed
        // to always be called sequentially. Otherwise the reference
        // should be immutable.
        let backend = &mut *(ptr as *mut Box<Backend>);
        backend.update()
    }
}

edited to fix into_raw calls, it's not a method

3 Likes

Where can I track when into_raw and from_raw might get into stable?

Oh sorry, didn't realize I wasn't looking at the 1.3 docs. Since they're stable in beta, that would be Rust 1.4.

1 Like

FYI, the general formula for finding out more info about unstable features is/will be* looking at the docs: the "Unstable" marker for a feature should include a link to some tracking issue on Github. E.g. std::ops::InPlace is currently unstable and points to #27779.

*This feature is currently only in beta, not stable yet (only 2 weeks until the next release!).

1 Like

I did in fact try that in this case; the markers on into_raw and from_raw don't seem to link to anything atm. But good to know that strategy to find this information should work in general :smile:

Yeah, as I said, this feature not yet quite made it to the stable branch yet, however the beta or nightly docs do show it ( std - Rust and std - Rust ) and it's only two weeks before the former becomes the stable branch, including this doc feature.

Thanks for taking the time to reply!

You suggestions looks good (not sure if I will go for dynamic dispatch or not but I will test to see what fits best)

Another question I have is how do I write this code:

#[no_mangle]
pub static mut g_backend: CBackendCallbacks = CBackendCallbacks { 
    create_instance: call_create_instance, 
    destroy_instance: call_destroy_instance, 
    update: call_update_instance 
};

when call_create_instance is a generic function, something like this

    create_instance: call_create_instance<MyBackend>, 

But that syntax doesn't work so I wonder if there is some other way to do it?

Cheers!

Type parameter syntax is a popular gotcha. You need to add extra :: in expressions like this:

    create_instance: call_create_instance::<MyBackend>,

The examples we exchanged yesterday were missing the extern qualifier. I hope the real code doesn't.


It seems you're in control of the API from both ends. I feel the lower level details could be encapsulated better if instead of a static array an exported init function returned an array of

typedef struct BackendPlugin
{
	const char* name;
	void* vtableHandle;
	void* instanceHandle;
} BackendPlugin;

With update and destroy like this

#[no_mangle]
pub extern fn destroy_instance(void* instanceHandle, void* vtableHandle);
#[no_mangle]
pub extern fn update_instance(void* instanceHandle, void* vtableHandle, Reader* inEvents, Writer* outEvents);

the two pointers combining into a trait object's fat pointer.

It seems you should be able to ask the implementor to export an extern "Rust" fn init from their crate and translate between the layers. That way they wouldn't have to notice any of the C business.

1 Like

Thanks!

Ahh... I tried to find info on it but failed :slight_smile:

Yeah that is a good suggestion and yes I control the API on both sides. The current API is easy to use from C which is why it's made that way.

I'm not really sure what you mean here with "translate between the layers"

[quote="emoon, post:11, topic:3249"]
I'm not really sure what you mean here with "translate between the layers"
[/quote]You make a crate plugin_glue which exports extern "C" fn init to the C side and import extern "Rust" fn backend_init from... somewhere (I don't have the details down and never tried this myself).

The user makes a crate my_backend exporting pub extern "Rust" fn backend_init. When all of this is linked together your init calls backend_init, which has a pure Rust signature.

I might be getting what links to what wrong but it seems this should be doable.

Ah right. I will give it a shot.

Thanks again!

Hi,

I made some progress on this and I made a combo of your idea any my :slight_smile: So the current code looks like this (from the users perspective)

#[macro_use]
extern crate prodbg;

struct MyBackend {
    some_data: i32,
}

impl prodbg::Backend for MyBackend {

    fn new() -> Self {
        MyBackend { some_data: 30 }
    }

    fn update(&mut self) {
        self.some_data += 1;
    }
}

#[no_mangle]
pub fn init_plugin(plugin_handler: &mut prodbg::PluginHandler) 
{
    let plugin = define_plugin!(MyBackend);
    plugin_handler.register(&plugin);
}

I think this is pretty fine.

One issue though that in order to get "init_plugin" to work it has to be exported as a C function. Like this

extern {
    fn init_plugin(_: &mut PluginHandler);
}

If I do extern "Rust" I get this https://github.com/rust-lang/rust/issues/29115

The way it works now is there is a "InitPlugin" that is exported that the C side calls.

#[allow(non_snake_case)]
#[no_mangle]
pub extern fn InitPlugin(cb: extern fn(plugin: *mut c_void, data: *mut c_void), priv_data: *mut c_void) {
    let mut plugin_handler = PluginHandler { 
        private_data : priv_data, 
        c_register_plugin : cb
    };

    unsafe {
        init_plugin(&mut plugin_handler);
    }
}

The define_plugin macro looks like this

#[macro_export]
macro_rules! define_plugin {
    ($x:ty) => {
        {
            let plugin = prodbg::CBackendCallbacks { 
                create_instance: prodbg::create_instance::<$x>, 
                destroy_instance: prodbg::destroy_instance::<$x>, 
                update: prodbg::update_instance::<$x> 
             };

            plugin
        }
    }
}

So currently the code is up and running when using regular extern instead of extern "Rust" but it feels a bit weird to have it like that.

Also another issue is that create_instance works as it should but when I call destroy_instance I get a "malloc pointer has not be allocated" issue but I'm going to look into that a bit more (the code looks correct and the pointer returned from create_instance is the same as being sent to destroy_instance)