Struct methods as FFI callbacks

Viewed 383

I'm working on a plugin for an existing host application. The application provides a native C SDK that I connect to from Rust with FFI bindings generated with bindgen. The SDK relies heavily on callback functions to inform my plugin of state changes and user interactions.

My plugin on the Rust side is basically a structholding the relevant data. All functionality is built into methods of this struct.

My challenge are those function callbacks needed by the SDK.

I can easily create static functions and hand them over to the host application:

// function to set callback function in FFI lib
fn set_callback(callback_fn: fn(usize)) {
    let ptr = callback_fn as *const ();
    unsafe {
        // transmute the fn ptr into a C-style fn
        std::mem::transmute::<*const (), Option<unsafe extern "C" fn(usize)>>(ptr);
        // this can now be handed over as the callback via FFI
        // ...
    }
}

// Rust function to be used as callback
fn my_callback(val: usize) {
    println!("{}", val);
}

set_callback(my_callback);

Those functions just don't have access to my struct and thus the data and functionality stored there. So I need to use methods of my struct.

If I convert those static functions into methods of my struct, I can't use them as callback anymore:

struct dummy {}

impl dummy {
    fn set_callback(&mut self, callback_fn: fn(usize)) {
        let ptr = callback_fn as *const ();
        unsafe {
            // transmute the fn ptr into a C-style fn
            std::mem::transmute::<*const (), Option<unsafe extern "C" fn(usize)>>(ptr);
            // this can now be handed over as the callback via FFI
            // ...
        }
    }
    fn my_callback(&self, val: usize) {
        // do something
    }

    fn init(&mut self) {
        self.set_callback(self.my_callback);
    }
}

let mut dummy_instance = dummy {};
dummy_instance.init();
error[E0615]: attempted to take value of method `my_callback` on type `&mut dummy`
   --> src/main.rs:18:36
    |
 18 |             self.set_callback(self.my_callback);
    |                                    ^^^^^^^^^^^ method, not a field
    |
help: use parentheses to call the method
    |
 18 |             self.set_callback(self.my_callback(_));
    |                                               ^^^

Seems that I'd have to use a closure encapsulating that function call. This creates the next obstacle for me, as I can't easily hand over that closure via FFI to call from C. I could wrap it like this:

use std::ffi::c_void;

type Callback<T> = unsafe extern "C" fn(*mut c_void, x: T) -> T;

fn c_like_closure<F, T>(_: &mut F) -> Callback<T>
where
    F: FnMut(T) -> T,
{
    unsafe extern "C" fn invoke<Q, S>(state: *mut c_void, x: S) -> S
    where
        Q: FnMut(S) -> S,
    {
        let func = &mut *(state as *mut Q);
        func(x)
    }

    invoke::<F, T>
}

struct Dummy {}

impl Dummy {
    fn my_callback(&self, val: usize) -> usize {
        val
    }
}

fn main() {

    let mut my_plugin = Dummy {};
    let mut cb = |val: usize| my_plugin.my_callback(val);

    let fn_ptr = c_like_closure(&mut cb);
    let g = unsafe { fn_ptr(&mut cb as *mut _ as *mut c_void, 123) };
}

The caveat is, that I'd have to somehow hand over the context. And the SDK I have to use doesn't offer any function parameters that would allow me top pass the context back and forth.

I've found libfficrate that seems to be able to wrap a closure and hand it over via FFI. The above example can be altered as following:

use libffi::high::arity1::Closure1;
...
fn main() {

    let mut my_plugin = Dummy {};
    let mut cb = |val: usize| my_plugin.my_callback(val);

    let closure = Closure1::new(&cb);
    let fn_ptr = closure.code_ptr();
    unsafe {
        fn_ptr(123);
    }
}

Nice!

Until I try to do it from inside my struct, within my init_pluginmethod. There I can't easily create a closure using self:

struct Dummy {
    closure: Option<Box<Fn(usize) -> usize>>,
}

impl Dummy {
    fn my_callback(&self, val: usize) -> usize {
        println!("{}", val);
        val
    }

    fn init_plugin(&mut self) {
        self.closure = Some(Box::new(|val: usize| self.my_callback(val)));
    }
}
error[E0759]: `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
  --> src/main.rs:64:38
   |
63 |     fn init_plugin(&mut self) {
   |                    --------- this data with an anonymous lifetime `'_`...
64 |         self.closure = Some(Box::new(|val: usize| self.my_callback(val)));
   |                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...is captured here, requiring it to live as long as `'static`

Looks like I'm stuck on my path with closures… The only other option I see would be some static intermediate functions that get a reference to my plugin struct from somewhere and then call the methods on that like this:

let my_plugin = Dummy{};

Dummy::my_callback(my_plugin, 123);

But that would require my_pluginto become a global variable, which doesn't sound too convincing to me…

It's quite obvious, that I'm not the only one, trying to work with callbacks via FFI. I wonder how this can be solved without giving up on Rust's principles and advantages.

0 Answers
Related