我正在尝试为一段 C API 编写一个 Rusty 包装器。有一个 C 结构让我很纠结:
typedef bool (*listener_t) (int, int);
bool do_it(int x1, int y1, int x2, int y2, listener_t listener)
除非监听器返回 false,否则该函数会针对一定范围内的数字执行其工作。在这种情况下,它会中止计算。我想要一个像这样的 Rust 包装器:
fn do_with_callback<F>(start: (i32, i32), end: (i32, i32), callback: F)
where F: Fn(i32, i32) -> bool
rust-bindgen
为我创建了这个,为清楚起见略作编辑:
pub type listener_t = Option<extern "C" fn(x: c_int, y: c_int) -> c_bool>;
pub fn TCOD_line(xFrom: c_int, yFrom: c_int,
xTo: c_int, yTo: c_int,
listener: listener_t) -> c_bool;
我应该如何在我的 do_with
函数中将闭包或特征引用转换为 C 风格的回调:
pub fn do_with_callback<F>(start: (i32, i32), end: (i32, i32), callback: F) -> Self
where F: Fn(i32, i32) -> bool
{
let wrapper = ???;
unsafe {
ffi::do_it(start.0, start.1, end.0, end.1, Some(wrapper))
};
}
最佳答案
除非 C API 允许传递用户提供的回调参数,否则您不能这样做。如果没有,则只能使用静态函数。
原因是闭包不只是函数。顾名思义,闭包从其词法范围“关闭”变量。每个闭包都有一个关联的数据片段,其中包含捕获变量的值(如果使用 move
关键字)或对它们的引用。这些数据可以被认为是一些未命名的、匿名的 struct
.
编译器会自动添加对应Fn*
的实现这些匿名结构的特征。 As you can see ,这些特征的方法接受 self
除了闭包参数。在这种情况下,self
是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
不兼容因为尺寸不同。
关于rust - 如何将 Rust 闭包转换为 C 风格的回调?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32270030/