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

查看:305
本文介绍了如何将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;

我应该如何在<$ c中将闭包或特征引用转换为C风格的回调$ c> do_with 函数:

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 是实现该特征的 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确实允许传递用户定义的参数,然后可以对特征对象进行所需的操作:

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< Fn(..)-> ..> 特质对象,并将其传递给函数后将其泄漏。然后,如果可能的话,应该从您的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< .. 。>> )是必需的,因为 Box< Fn(..)-> ..> 是一个特征对象,因此是一个胖指针,由于大小不同,它与 * 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天全站免登陆