gpt4 book ai didi

rust - 如何将 Rust 闭包转换为 C 风格的回调?

转载 作者:行者123 更新时间:2023-11-29 07:41:23 25 4
gpt4 key购买 nike

我正在尝试为一段 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除了闭包参数。在这种情况下,selfstruct在其上实现特征。这意味着对应于闭包的每个函数还有一个包含闭包环境的附加参数。

如果您的 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/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com