Registering rust functions as callbacks for a C library API


#1

I’m wrapping bindings generated by rust-bindgen to give a rusty flavour to the APIs.

For example,

For the below binding

pub fn mosquitto_connect(mosq: *mut Struct_mosquitto,
                             host: *const ::libc::c_char, port: ::libc::c_int,
                             keepalive: ::libc::c_int) -> ::libc::c_int;

The wrapper is as below

struct Mosquitto{
    mosquitto: * mut mosquitto::Struct_mosquitto,
}

impl Mosquitto{
    fn connect(&self, host: &str, port: i32, keepalive: i32) -> i32{
        let host = CString::new(host.to_owned()).unwrap().as_ptr();
        unsafe{
            mosquitto::mosquitto_connect(self.mosquitto, host, port, keepalive)
        }
    }
}

I want to do same thing for the below binding. But this involves a callback.

pub fn mosquitto_publish_callback_set(mosq: *mut Struct_mosquitto, on_publish: ::std::option::Option<
                extern "C" fn(arg1: *mut Struct_mosquitto, arg2: *mut ::libc::c_void, arg3: ::libc::c_int) -> ( )>)

I’m not sure what to do here.

impl Mosquitto{
    fn register_onpublish_callback(&self, callback_closure){
        unsafe{
            mosquitto_publish_callback_set(self.mosquitto ...............
         }
    }
}

In the above API, function registered by on_publish is called asynchronously when some publish happens.

Instead of providing fn(arg1: *mut Struct_mosquitto, arg2: *mut ::libc::c_void, arg3: ::libc::c_int) -> ( ) as callback, users of this package should be able to provide a closure as a callback to register_onpublish_callback so that the closure will be triggered when some publish happens.


#2

How do I create a Rust callback function to pass to a FFI function?


#3

I’ve tried this and it works. What I want is different.

  1. I want to wrap mosquitto_publish_callback_set with in a method.

  2. It should be able to take a closure instead of fn(arg1: *mut Struct_mosquitto, arg2: *mut ::libc::c_void, arg3: ::libc::c_int) -> ( )

I’ve updated the question.


#4

Based on the code you’ve shown, you can’t; at least, not directly. You can’t turn a closure into a function pointer, because it requires an additional piece of context, and that call doesn’t look like it has any way to provide that context.

If you can control the contents of that *mut c_void argument, you can use that. If not… the only other option would be to use a global that maps (*mut Struct_mosquitto, Event) to a context, but you probably want to avoid that if at all possible.


#5

This is interesting.

fn register_onconnect_callback<F>(&self, f: F) where F: Fn(){
        unsafe{
            mosquitto::mosquitto_connect_callback_set(self.mosquitto, Some(onconnect_wrapper::<F>));
        }
        extern "C" fn onconnect_wrapper<F>(mqtt: *mut mosquitto::Struct_mosquitto, closure: *mut libc::c_void, val: libc::c_int)
        where F:Fn(){
            let closure = closure as *mut F;
            unsafe{
                let res = (*closure)();
            }
        }
    }


fn main(){
    mqtt.register_onconnect_callback(||println!("@@@ On connect callback @@@"));
}

#6

I’m not familiar with whatever library you’re using, but unless I’m missing something significant:

DO NOT DO THIS!

It is only working because that closure doesn’t capture anything, and thus its environment is effectively empty. This means the closure is effectively equivalent to a bare function.

It’s incredibly dangerous and only works by coincidence.