如何将 Rust 闭包转换为 C 风格的回调? [英] How do I convert a Rust closure to a C-style callback?

查看:23
本文介绍了如何将 Rust 闭包转换为 C 风格的回调?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为一段 C API 编写一个 Rusty 包装器.有一个 C 结构我很挣扎:

I'm trying to write a Rusty wrapper for a piece of C API. There is one C construct I struggle with:

typedef bool (*listener_t) (int, int);
bool do_it(int x1, int y1, int x2, int y2, listener_t listener)

除非侦听器返回 false,否则该函数会针对一系列数字执行其工作.在这种情况下,它会中止计算.我想要一个像这样的 Rust 包装器:

The function does its job for a range of numbers unless the listener returns false. In that case it aborts computation. I want to have a Rust wrapper like this:

fn do_with_callback<F>(start: (i32, i32), end: (i32, i32), callback: F)
    where F: Fn(i32, i32) -> bool

rust-bindgen 为我创建了这个,为了清晰起见略作

rust-bindgen created this for me, slightly edited for clarity:

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 风格的回调:

How should I convert a closure or a trait reference to a C-style callback in my do_with functions:

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 允许传递用户提供的回调参数,否则您不能这样做.如果没有,则只能使用静态函数.

You cannot do it unless the C API allows passing a user-provided callback parameter. If it does not, you can only use static functions.

原因是闭包不仅仅是只是"函数.顾名思义,闭包从它们的词法范围关闭"变量.每个闭包都有一个相关联的数据,它保存捕获变量的值(如果使用 move 关键字)或对它们的引用.这些数据可以被认为是一些未命名的、匿名的struct.

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. This data can be thought of as some unnamed, anonymous struct.

编译器会自动为这些匿名结构添加相应的 Fn* 特征的实现.如您所见,这些特征的方法接受self 除了闭包参数.在这种情况下,self 是实现 trait 的 struct.这意味着对应于一个闭包的每个函数也有一个包含闭包环境的附加参数.

The compiler automatically adds an implementation of the corresponding Fn* traits for these anonymous structs. As you can see, methods on these traits accept self in addition to the closure arguments. In this context, self is the struct on which the trait is implemented. This means that each function which corresponds to a closure also has an additional parameter which contains the closure environment.

如果您的 C API 只允许您传递没有任何用户定义参数的函数,则您无法编写允许您使用闭包的包装器.我想可能可以为闭包环境编写一些全局持有者,但我怀疑这是否容易和安全.

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. I guess it may be possible to write some global holder for the closures environment, but I doubt it would be easy and safe.

如果你的 C API 允许传递用户定义的参数,那么你可以用 trait 对象做你想做的事:

If your C API does allow passing a user-defined argument, then it is possible to do what you want with trait objects:

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 }
}

这仅在 do_something 不将指向回调的指针存储在某处时才有效.如果是这样,您需要使用 Box..> trait 对象,并在将其传递给函数后将其泄漏.然后,如果可能,应该从您的 C 库中取回它并处理掉.它可能看起来像这样:

This will only work if do_something does not store the pointer to the callback somewhere. If it does, you need to use a Box<Fn(..) -> ..> trait object and leak it after you pass it to the function. Then, if possible, it should be obtained back from your C library and disposed of. 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();
}

双重间接(即Box>)是必要的,因为Box..> 是一个 trait 对象,因此是一个胖指针,由于大小不同,与 *mut c_void 不兼容.

Double indirection (i.e. 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.

这篇关于如何将 Rust 闭包转换为 C 风格的回调?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆