Is it possible to pass a closure through the FFI boundary?


#1

I wrote a rust lib, and want to use it in C++, the design pattern of one function requires passing a closure through the FFI boundary:

fn some_func_return_a_closure()->Box<Fn() -> i32>{...}

I want to call some_function_return_a_closure from C++, so that I wrapped it as

#[no_mangle]
pub unsafe extern "C" fn wrap_func() -> ?? {...}

The ?? should be some wrapper of the origin return type Box<Fn()->i32>.

My question is that is it possible and how can I, to return a wrapped closure and pass it to the C++ caller?

I have tried to write a wrapper struct as

#[repr(C)]
struct fn_wrapper{
    pf:*mut Fn<...>
}

Obviously , this does not work.

Of course there should be some other method which can realize the same function, but with some (in my opinion) unnatural way, but is it possible to implement above design pattern?


#2

What exactly didn’t work with fn_wrapper approach?


#3

Certainly, I can construct a struct with a *mut Fn<…> data member, and make it repr©, but cbindgen cannot generate corresponding header that contains corresponding definition in C. Instead, only a forward declaration. This makes it impossible to use it as a stack variable, but only a pointer.

So, perhaps, I can wrap it with another layer? i.e., first wrap a Box with a struct, then write another struct, containing a *mut pointer to the former struct.


#4

Yeah, you can add more indirection. You can also pass a ptr to C++ code rather than a value - the ptr can be to a stack allocated wrapper if the lifetimes work out (ie C++ code doesn’t hold on to it).

You’d have a much easier time if you didn’t use a closure, of course, but a plain extern fn - you can have some_func_return_a_closure call that same fn internally to avoid duplication:

fn main() {}

#[no_mangle]
pub extern "C" fn my_func() -> i32 {
    5
}

fn some_func_return_a_closure() -> Box<Fn() -> i32> {
    Box::new(|| my_func())
}

#5

The standard way to pass a closure through FFI is to separate out the data and fn pointers, like so:

fn closure_to_ffi<F: Fn(i32, i32) -> i32>(f: F) -> (*mut F, unsafe extern "C" fn(*mut F, i32, i32) -> i32) {
    unsafe extern "C" fn call_closure_from_ffi<F: Fn(i32, i32) -> i32>(f: *mut F, a: i32, b: i32) -> i32 {
        (*f)(a, b)
    }
    (Box::into_raw(Box::new(f)), call_closure_from_ffi::<F>)
}