我正在尝试为一块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))
};
}
3 回答
除非C API允许传递用户提供的回调参数,否则您无法执行此操作 . 如果没有,则只能使用静态功能 .
原因是闭包不是"just"函数 . 顾名思义,从词法范围封闭变量 . 每个闭包都有一个相关的数据,它包含捕获变量的值(如果使用
move
关键字)或对它们的引用 . 这些数据可以被认为是一些未命名的,匿名的struct
.编译器会自动为这些匿名结构添加相应
Fn*
特征的实现 . As you can see,这些特征的方法除了闭包参数外还接受self
. 在此上下文中,self
是实现特征的struct
. 这意味着对应于闭包的每个函数也有一个包含闭包环境的附加参数 .如果您的C API仅允许您传递没有任何用户定义参数的函数,则无法编写允许您使用闭包的包装器 . 我想有可能为封闭环境编写一些全局持有者,但我怀疑它是否容易和安全 .
如果您的C API允许传递用户定义的参数,那么可以使用特征对象执行您想要的操作:
这仅在
do_something
未将指针存储到某处的回调时才有效 . 如果是,则需要使用Box<Fn(..) -> ..>
trait对象并在将其传递给函数后将其泄漏 . 然后,如果可能的话,应该从C库中获取并处理掉它 . 它可能看起来像这样:双重间接(即
Box<Box<...>>
)是必要的,因为Box<Fn(..) -> ..>
是一个特征对象,因此是一个胖指针,由于大小不同而与*mut c_void
不兼容 .在C中,函数指针没有关联的上下文,这就是为什么C回调函数通常带有额外的
void*
参数传递上下文......或者有一个API来存储用户数据......
如果要包装的库不提供上述任何一个,则需要通过其他通道传递上下文,例如:通过全局变量,尽管该上下文将在整个过程中共享:
也可以创建一个trampoline function直接在函数中粘贴上下文,但这是非常困难和不安全的 .
从https://stackoverflow.com/a/42597209/224671手动迁移的答案
The first snippet from Vladimir Matveev不再像书面那样工作 .
&mut FnMut(i32) -> bool
和*mut c_void
的大小不同,这样的演员阵容会导致崩溃 . 更正示例(playpen):