Hi,
I'm trying to write a binding for a native C/C++ library that employs callbacks.
This is the code that registers a callback:
pub fn foo_register<F>(&self, cb: F) -> TraitObject where F: Fn(i32) {
let to = unsafe {
glue::foo_register(self.0, mem::transmute(&cb as &Fn(i32)))
};
mem::forget(cb);
to
}
#[no_mangle]
pub extern fn foo_trampoline(callback: TraitObject) {
let cb: &Fn(i32) = unsafe { mem::transmute(callback) };
cb(3);
}
The glue
is a C glue code that does the actual registering. I have an equivalent of TraitObject
defined in C, the function saves that and a native callback function passes that to a rust trampoline function, which turns it back into the original closure type.
I also have the glue register function call the trampoline just to verify (which works).
And then I also have the rust register function return the trait object as well for testing purposes.
Usage:
struct Droptest(i32);
impl Drop for Droptest {
fn drop(&mut self) {
println!("Dropped! ({})", self.0);
}
}
fn main() {
let ctx = unsafe { glue::ctx_create() };
let d = Droptest(1);
let to = ctx.foo_register(move |arg: i32| {
println!("Callback called! *{:p} = {}", &d, d.0);
});
foo_trampoline(to);
unsafe { glue::ctx_do_something(ctx); }
}
ctx_do_something()
triggers the callback.
The output:
Callback called! *0x7ffd98087bf8 = 1
Callback called! *0x7ffd98087bf8 = 1495883296
Callback called! *0x7ffd98087bf8 = 1493443512
The first line is when the closure is called while registering, just to verify that the pass-through through C/C++ code works. The second line is calling foo_trampoline()
on the trait object returned from the register function. By this time, the closure already contains invalid data.
The third line is the native library triggering the callback, same story as the second line.
I also tried passing the closure by a trait object right from the main function, ie:
pub fn foo_register<F>(&self, cb: &F) -> TraitObject where F: Fn(i32) {
//...
let to = ctx.foo_register(& move |arg: i32| {
println!("Callback called! *{:p} = {}", &d, d.0);
});
//...
With that varitaion, I also get a drop logged from the Droptest
thingy:
Callback called! *0x7ffd98087bf8 = 1
Dropped! (1)
Callback called! *0x7ffd98087bf8 = -2102046192
Callback called! *0x7ffd98087bf8 = 281314120
Both of those seem really weird to me.
I would've expected the mem::forget()
to prevent the closure from being dropped.
How do I prevent the closure and it's environment from being dropped?
Thanks.