Passing a closure to an external C FFI Library

I am attempting to retrieve a Vec of backend name strings from a C function. The function signature is as follows:

// Invokes a callback with each registered backend.
void EnumerateBackends(
    void (*callback)(const char *backend, void *userData),
    session_t session,
    void *userData
);

Upon investigation, it appears that the callback function is only called while the EnumerateBackends function is still executing. I have tried defining

static BACKENDS: Mutex<Vec<String>> = Mutex::new(Vec::new());

However, this solution can lead to race conditions if multiple sessions are spawned concurrently. I also attempted to use the libffi-rs to pass FnMut closures into the FFI function, but encountered some unusual errors. Do you have any suggestions for a robust way to solve this problem?

Closures in Rust may have additional context data, which makes them incompatible with context-free C function pointers.

You will need to use user_data to pass the closure, and the function pointer needs to be a C function that will cast user_data to the closure type and call it.

You can't use Box<dyn Fn> or &mut dyn Fn as the user_data pointer, because this is a fat pointer, and C only supports thin pointers (you could pass a pointer to a fat pointer (double indirection)).

Here's the third version of the code I wrote for it. It was cumbersome to make it safe and pass Miri:

use std::os::raw::c_void;
use std::marker::PhantomData;

pub struct CCallback<'closure, Arg, Ret> {
    pub function: unsafe extern "C-unwind" fn(*mut c_void, Arg) -> Ret,
    pub user_data: *mut c_void,

    // without this it's too easy to accidentally drop the closure too soon
    _lifetime: PhantomData<&'closure mut c_void>,
}

impl<'closure, Arg, Ret> CCallback<'closure, Arg, Ret> {
    pub fn new<F>(closure: &'closure mut F) -> Self where F: FnMut(Arg) -> Ret {
        let function: unsafe extern "C-unwind" fn(*mut c_void, Arg) -> Ret = Self::call_closure::<F>;

        debug_assert_eq!(std::mem::size_of::<&'closure mut F>(), std::mem::size_of::<*const c_void>());
        debug_assert_eq!(std::mem::size_of_val(&function), std::mem::size_of::<*const c_void>());

        Self {
            function,
            user_data: closure as *mut F as *mut c_void,
            _lifetime: PhantomData,
        }
    }

    unsafe extern "C-unwind" fn call_closure<F>(user_data: *mut c_void, arg: Arg) -> Ret where F: FnMut(Arg) -> Ret {
        let cb: &mut F = user_data.cast::<F>().as_mut().unwrap();
        (*cb)(arg)
    }
}

fn main() {
    let mut v = Vec::new();

    // must assign to a variable to ensure it lives until the end of scope
    let closure = &mut |x: i32| { v.push(x) };
    let c = CCallback::new(closure);

    unsafe { (c.function)(c.user_data, 123) };
    
    assert_eq!(v, [123]);
}
3 Likes

With &mut &mut F, isn't the inner reference dropped at the end of the line in which it's created? Why is the subsequent unsafe code sound?

Yikes, you're right. I've updated the code.

1 Like

Don't you have to catch a potential clousure panic?

Ok, I've changed the ABI to C-unwind. Not my problem now :slight_smile:

1 Like