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

标签 rust closures ffi

我正在尝试为一段 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/

相关文章:

javascript - 我怎么会误解这个例子中的 JavaScript 闭包解析?

rust - Rust 中的部分应用程序是否有开销?

perl - 通过 FFI::Platypus 在 Perl 中获取缓冲区大小

rust - 如何从 Rust 访问用 C 声明的函数指针的零终止数组?

rust - 为什么在 .split() 之后我得到一个空的 &str?

rust - 如何在 Rust 中将 bson::Bson 转换为 Vec<u8> ?

ios - 使用 NSURL 的结果填充 UIView

rust - 为什么使用带有 into 的涡轮鱼会给出 "wrong number of type arguments"?

pointers - 有没有安全的替代方法可以将不安全的slice::from_raw_parts替换为原始指针?

ffi - 如何在 PureScript FFI 中映射 0 参数 JavaScript 函数