如何将 Rust 闭包转换为 C 风格的回调?
How do I convert a Rust closure to a C-style callback?
我正在尝试为一段 C API 编写一个 Rusty 包装器。有一种 C 结构令我苦恼:
typedef bool (*listener_t) (int, int);
bool do_it(int x1, int y1, int x2, int y2, listener_t listener)
除非侦听器 returns 为 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 确实允许传递用户定义的参数,那么你可以用特征对象做你想做的事:
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(..) -> ..>
特征对象并在将其传递给函数后将其泄漏。然后,如果可能的话,应该从您的 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
不兼容。
The first snippet from Vladimir Matveev no longer works as written. The size of &mut FnMut(i32) -> bool
and *mut c_void
is different and such casts lead to a crash. 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
}));
}
在 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)
...或者有一个API来存储用户数据...
void api_set_user_data(void* user_data); // <-- caller set the context
void* api_get_user_data(); // <-- callback use this to retrieve context.
如果您要包装的库不提供上述任何内容,您将需要通过其他渠道传递上下文,例如通过全局变量,尽管该上下文将在整个过程中共享:
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);
}
}
也可以创建一个trampoline function直接在函数中粘贴上下文,但是极其困难且不安全。
从
手动迁移的答案
我正在尝试为一段 C API 编写一个 Rusty 包装器。有一种 C 结构令我苦恼:
typedef bool (*listener_t) (int, int);
bool do_it(int x1, int y1, int x2, int y2, listener_t listener)
除非侦听器 returns 为 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 确实允许传递用户定义的参数,那么你可以用特征对象做你想做的事:
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(..) -> ..>
特征对象并在将其传递给函数后将其泄漏。然后,如果可能的话,应该从您的 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
不兼容。
The first snippet from Vladimir Matveev no longer works as written. The size of &mut FnMut(i32) -> bool
and *mut c_void
is different and such casts lead to a crash. 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
}));
}
在 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)
...或者有一个API来存储用户数据...
void api_set_user_data(void* user_data); // <-- caller set the context
void* api_get_user_data(); // <-- callback use this to retrieve context.
如果您要包装的库不提供上述任何内容,您将需要通过其他渠道传递上下文,例如通过全局变量,尽管该上下文将在整个过程中共享:
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);
}
}
也可以创建一个trampoline function直接在函数中粘贴上下文,但是极其困难且不安全。
从
手动迁移的答案