我正试图为一段C API编写一个Rust 的包装.有一个C struct 我很难理解:

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.

编译器会自动为这些匿名 struct 添加相应Fn*个特征的实现.As you can see,除闭包参数外,这些特性上的方法接受self.在这种情况下,self是实现trait的struct.每个闭包都包含一个与此环境相对应的闭包.

如果您的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相关问答推荐

交叉术语未正确清除屏幕

如何在Tauri中将变量从后端传递到前端

获取字符串切片(&;str)上的切片([ia..ib])返回字符串

制作一片连续整数的惯用Rust 方法?

为什么我们需要std::thread::scope,如果我们可以使用thread.join()在函数的生命周期内删除引用?

如何将映射反序列化为具有与键匹配的字段的定制 struct 的向量?

在Rust中,Box:ed struct 与普通 struct 在删除顺序上有区别吗?

如何轮询 Pin>?

Cargo.toml:如何有条件地启用依赖项功能?

为什么`tokio::main`可以直接使用而不需要任何导入?

是否可以通过可变引用推进可变切片?

特征中定义的类型与一般定义的类型之间的区别

Rust 将特性传递给依赖项

LinkedList::drain_filter::drop 中 DropGuard 的作用是什么?

使用 HashMap 条目时如何避免字符串键的短暂克隆?

当 `T` 没有实现 `Debug` 时替代 `unwrap()`

匹配结果时的简洁日志(log)记录

相交着色器从 SSBO 中读取零

Rust - 在线程之间不安全地共享没有互斥量的可变数据

在 Rust 中枚举字符串的最佳方式? (字符()与 as_bytes())