[英]How do I convert a Rust closure to a C-style callback?
I'm trying to write a Rusty wrapper for a piece of C API. 我正在尝试为一段C API编写Rusty包装器。 There is one C construct I struggle with:
我遇到一个C构造:
typedef bool (*listener_t) (int, int);
bool do_it(int x1, int y1, int x2, int y2, listener_t listener)
The function does its job for a range of numbers unless the listener returns false. 除非侦听器返回false,否则该函数将针对一定范围的数字执行其工作。 In that case it aborts computation.
在这种情况下,它将中止计算。 I want to have a Rust wrapper like this:
我想要一个这样的Rust包装器:
fn do_with_callback<F>(start: (i32, i32), end: (i32, i32), callback: F)
where F: Fn(i32, i32) -> bool
rust-bindgen
created this for me, slightly edited for clarity: 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;
How should I convert a closure or a trait reference to a C-style callback in my do_with
functions: 我应该如何在
do_with
函数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))
};
}
You cannot do it unless the C API allows passing a user-provided callback parameter. 除非C API允许传递用户提供的回调参数,否则您不能这样做。 If it does not, you can only use static functions.
如果不是,则只能使用静态函数。
The reason is that closures are not "just" functions. 原因是闭包不是“公正的”功能。 As their name implies, closures "close over" variables from their lexical scope.
顾名思义,闭包将变量从其词法范围“关闭”。 Each closure has an associated piece of data which holds either values of captured variables (if the
move
keyword is used) or references to them. 每个闭包都有一条关联的数据,其中包含捕获的变量的值(如果使用
move
关键字)或对其的引用。 This data can be thought of as some unnamed, anonymous struct
. 可以将这些数据视为一些未命名的匿名
struct
。
The compiler automatically adds an implementation of the corresponding Fn*
traits for these anonymous structs. 编译器会自动为这些匿名结构添加相应的
Fn*
特征的实现。 As you can see , methods on these traits accept self
in addition to the closure arguments. 如您所见 ,这些特性的方法除闭包参数外还接受
self
。 In this context, self
is the struct
on which the trait is implemented. 在这种情况下,
self
是实现特质的struct
。 This means that each function which corresponds to a closure also has an additional parameter which contains the closure environment. 这意味着对应于闭包的每个函数还具有一个包含闭包环境的附加参数。
If your C API only allows you to pass functions without any user-defined parameters, you cannot write a wrapper which would allow you to use closures. 如果您的C API仅允许您传递不带任何用户定义参数的函数,则无法编写允许使用闭包的包装器。 I guess it may be possible to write some global holder for the closures environment, but I doubt it would be easy and safe.
我猜可能为闭包环境编写一些全局持有人,但我怀疑这样做是否简单又安全。
If your C API does allow passing a user-defined argument, then it is possible to do what you want with trait objects: 如果您的C API确实允许传递用户定义的参数,则可以对特征对象进行所需的操作:
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 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 FnMut(i32) -> bool = &mut callback;
let cb = &mut cb;
unsafe { do_something(Some(do_something_handler), cb as *mut _ as *mut c_void) > 0 }
}
This will only work if do_something
does not store the pointer to the callback somewhere. 仅当
do_something
未将指向回调的指针存储在某处时,这才起作用。 If it does, you need to use a Box<Fn(..) -> ..>
trait object and leak it after you pass it to the function. 如果是这样,则需要使用
Box<Fn(..) -> ..>
特征对象,并将其传递给函数后将其泄漏。 Then, if possible, it should be obtained back from your C library and disposed of. 然后,如果可能的话,应该从您的C库中获取并进行处理。 It could look like this:
它可能看起来像这样:
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<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<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<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();
}
Double indirection (ie Box<Box<...>>
) is necessary because Box<Fn(..) -> ..>
is a trait object and therefore a fat pointer, incompatible with *mut c_void
because of different size. 由于
Box<Fn(..) -> ..>
是特征对象,因此是胖指针,由于大小不同而与*mut c_void
不兼容,因此必须使用双重间接寻址(即Box<Box<...>>
)。
In C, a function pointer does not have associated context, which is why usually a C callback function usually carry an extra void*
argument pass the context... 在C语言中,函数指针没有关联的上下文,这就是为什么通常C回调函数通常在上下文中传递额外的
void*
参数的原因...
typedef bool (*listener_t)(int, int, void* user_data);
bool do_it(void* user_data, int x1, int y1, int x2, int y2, listener_t listener)
... or have an API to let to store the user data... ...或拥有一个用于存储用户数据的API ...
void api_set_user_data(void* user_data); // <-- caller set the context
void* api_get_user_data(); // <-- callback use this to retrieve context.
If the library you want to wrap does not provide any of the above, you will need to pass the context via other channels, eg via a global variable, though that context will be shared across the whole process: 如果您要包装的库没有提供上述任何内容,则您将需要通过其他渠道(例如,通过全局变量)传递上下文,尽管该上下文将在整个过程中共享:
lazy_static! {
static ref REAL_CALLBACK: Mutex<Option<Box<FnMut(c_int, c_int) -> bool + Send>>> = Default::default();
}
extern "C" fn callback(x: c_int, y: c_int) -> bool {
if let Some(ref mut real_callback) = *REAL_CALLBACK.lock().unwrap() {
real_callback(x, y)
} else {
panic!("<handle error here>");
}
}
fn main() {
*REAL_CALLBACK.lock().unwrap() = Some(Box::new(move |x, y| {
println!("...");
true
}));
unsafe {
do_it(callback);
}
}
It is also possible to create a trampoline function to stick the context directly in the function, but it is extremely difficult and unsafe. 也可以创建一个蹦床功能以将上下文直接保留在该功能中,但这是极其困难且不安全的。
Answer manually migrated from https://stackoverflow.com/a/42597209/224671 从https://stackoverflow.com/a/42597209/224671手动迁移的答案
The first snippet from Vladimir Matveev no longer works as written. 弗拉基米尔·马特维耶夫(Vladimir Matveev)的第一个代码段不再按书面形式工作。 The size of
&mut FnMut(i32) -> bool
and *mut c_void
is different and such casts lead to a crash. &mut FnMut(i32) -> bool
和*mut c_void
大小不同,这样的强制转换会导致崩溃。 Corrected example ( playpen ): 更正的示例(游戏围栏 ):
extern crate libc;
use std::mem::*;
use libc::c_void;
pub fn run<F>(mut callback: F) -> bool
where F: FnMut(i32) -> bool
{
let mut cb: &mut FnMut(i32) -> bool = &mut callback;
println!("sizeof(cb/*-ptr): {}/{}",
size_of::<*mut FnMut(i32) -> bool>(),
size_of::<*mut c_void>());
let ctx = &mut cb as *mut &mut FnMut(i32) -> bool as *mut c_void;
println!("ctx: {:?}", ctx);
//----------------------------------------------------------
// Convert backward
let cb2: *mut *mut FnMut(i32) -> bool = unsafe { transmute(ctx) };
println!("cb2: {:?}", cb2);
// this is more useful, but can't be printed, because not implement Debug
let closure: &mut &mut FnMut(i32) -> bool = unsafe { transmute(ctx) };
closure(0xDEAD)
}
fn main() {
println!("answer: {}",
run(|x| {
println!("What can change nature of a man?");
x > 42
}));
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.