除非C API允许传递用户提供的回调参数,否则无法执行此操作.如果没有,则只能使用静态函数.
原因是闭包不是"仅仅"函数.顾名思义,闭包从词法范围"关闭"变量.每个闭包都有一段关联的数据,其中包含捕获变量的值(如果使用move
关键字)或对它们的引用.这些数据可以被认为是一些匿名的struct
.
编译器会自动为这些匿名 struct 添加相应Fn*
个特征的实现.As you can see,除闭包参数外,这些特性上的方法接受self
.在这种情况下,self
是实现trait的struct
.每个闭包都包含一个与此环境相对应的闭包.
如果您的C API只允许您在没有任何用户定义参数的情况下传递函数,那么您就无法编写允许您使用闭包的包装器.我想可能会为闭包环境编写一些全球持有者,但我怀疑这是否容易和安全.
如果您的C API允许传递用户定义的参数,那么就可以对trait对象执行您想要的操作:
extern crate libc;
use std::mem;
use libc::{c_int, c_void};
extern "C" {
fn do_something(f: Option<extern "C" fn(x: c_int, arg: *mut c_void) -> c_int>, arg: *mut c_void) -> c_int;
}
extern "C" fn do_something_handler(x: c_int, arg: *mut c_void) -> c_int {
let closure: &mut &mut dyn FnMut(i32) -> bool = unsafe { mem::transmute(arg) };
closure(x as i32) as c_int
}
pub fn do_with_callback<F>(x: i32, mut callback: F) -> bool
where F: FnMut(i32) -> bool
{
// reason for double indirection is described below
let mut cb: &mut dyn FnMut(i32) -> bool = &mut callback;
let cb = &mut cb;
unsafe { do_something(Some(do_something_handler), cb as *mut _ as *mut c_void) > 0 }
}
只有当do_something
没有将指向回调的指针存储在某个地方时,这才有效.如果是这样,则需要使用Box<Fn(..) -> ..>
trait对象,并在将其传递给函数后将其泄漏.然后,如果可能的话,应该从您的C库中获取并处理它.它可能是这样的:
extern crate libc;
use std::mem;
use libc::{c_int, c_void};
extern "C" {
fn set_handler(f: Option<extern "C" fn(x: c_int, arg: *mut c_void) -> c_int>, arg: *mut c_void);
fn invoke_handler(x: c_int) -> c_int;
fn unset_handler() -> *mut c_void;
}
extern "C" fn do_something_handler(x: c_int, arg: *mut c_void) -> c_int {
let closure: &mut Box<dyn FnMut(i32) -> bool> = unsafe { mem::transmute(arg) };
closure(x as i32) as c_int
}
pub fn set_callback<F>(callback: F)
where F: FnMut(i32) -> bool,
F: 'static
{
let cb: Box<Box<dyn FnMut(i32) -> bool>> = Box::new(Box::new(callback));
unsafe {
set_handler(Some(do_something_handler), Box::into_raw(cb) as *mut _);
}
}
pub fn invoke_callback(x: i32) -> bool {
unsafe { invoke_handler(x as c_int) > 0 }
}
pub fn unset_callback() {
let ptr = unsafe { unset_handler() };
// drop the callback
let _: Box<Box<dyn FnMut(i32) -> bool>> = unsafe { Box::from_raw(ptr as *mut _) };
}
fn main() {
let mut y = 0;
set_callback(move |x| {
y += 1;
x > y
});
println!("First: {}", invoke_callback(2));
println!("Second: {}", invoke_callback(2));
unset_callback();
}
双间接寻址(即Box<Box<...>>
)是必要的,因为Box<Fn(..) -> ..>
是一个特征对象,因此是一个胖指针,由于大小不同,与*mut c_void
不兼容.