简体   繁体   English

如何将Rust闭包转换为C样式的回调?

[英]How do I convert a Rust closure to a C-style callback?

I'm trying to write a Rusty wrapper for a piece of C API. 我正在尝试为一段C API编写Rusty包装器。 There is one C construct I struggle with: 我遇到一个C构造:

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

The function does its job for a range of numbers unless the listener returns false. 除非侦听器返回false,否则该函数将针对一定范围的数字执行其工作。 In that case it aborts computation. 在这种情况下,它将中止计算。 I want to have a Rust wrapper like this: 我想要一个这样的Rust包装器:

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

rust-bindgen created this for me, slightly edited for clarity: 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;

How should I convert a closure or a trait reference to a C-style callback in my do_with functions: 我应该如何在do_with函数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))
    };
}

You cannot do it unless the C API allows passing a user-provided callback parameter. 除非C API允许传递用户提供的回调参数,否则您不能这样做。 If it does not, you can only use static functions. 如果不是,则只能使用静态函数。

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. 每个闭包都有一条关联的数据,其中包含捕获的变量的值(如果使用move关键字)或对其的引用。 This data can be thought of as some unnamed, anonymous struct . 可以将这些数据视为一些未命名的匿名struct

The compiler automatically adds an implementation of the corresponding Fn* traits for these anonymous structs. 编译器会自动为这些匿名结构添加相应的Fn*特征的实现。 As you can see , methods on these traits accept self in addition to the closure arguments. 如您所见 ,这些特性的方法除闭包参数外还接受self In this context, self is the struct on which the trait is implemented. 在这种情况下, self是实现特质的struct This means that each function which corresponds to a closure also has an additional parameter which contains the closure environment. 这意味着对应于闭包的每个函数还具有一个包含闭包环境的附加参数。

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. 如果您的C API仅允许您传递不带任何用户定义参数的函数,则无法编写允许使用闭包的包装器。 I guess it may be possible to write some global holder for the closures environment, but I doubt it would be easy and safe. 我猜可能为闭包环境编写一些全局持有人,但我怀疑这样做是否简单又安全。

If your C API does allow passing a user-defined argument, then it is possible to do what you want with trait objects: 如果您的C API确实允许传递用户定义的参数,则可以对特征对象进行所需的操作:

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

This will only work if do_something does not store the pointer to the callback somewhere. 仅当do_something未将指向回调的指针存储在某处时,这才起作用。 If it does, you need to use a Box<Fn(..) -> ..> trait object and leak it after you pass it to the function. 如果是这样,则需要使用Box<Fn(..) -> ..>特征对象,并将其传递给函数后将其泄漏。 Then, if possible, it should be obtained back from your C library and disposed of. 然后,如果可能的话,应该从您的C库中获取并进行处理。 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();
}

Double indirection (ie 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. 由于Box<Fn(..) -> ..>是特征对象,因此是胖指针,由于大小不同而与*mut c_void不兼容,因此必须使用双重间接寻址(即Box<Box<...>> )。

In C, a function pointer does not have associated context, which is why usually a C callback function usually carry an extra void* argument pass the context... 在C语言中,函数指针没有关联的上下文,这就是为什么通常C回调函数通常在上下文中传递额外的void*参数的原因...

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

... or have an API to let to store the user data... ...或拥有一个用于存储用户数据的API ...

void api_set_user_data(void* user_data);   // <-- caller set the context
void* api_get_user_data();   // <-- callback use this to retrieve context.

If the library you want to wrap does not provide any of the above, you will need to pass the context via other channels, eg via a global variable, though that context will be shared across the whole process: 如果您要包装的库没有提供上述任何内容,则您将需要通过其他渠道(例如,通过全局变量)传递上下文,尽管该上下文将在整个过程中共享:

lazy_static! {
    static ref REAL_CALLBACK: Mutex<Option<Box<FnMut(c_int, c_int) -> bool + Send>>> = Default::default();
}

extern "C" fn callback(x: c_int, y: c_int) -> bool {
    if let Some(ref mut real_callback) = *REAL_CALLBACK.lock().unwrap() {
        real_callback(x, y)
    } else {
        panic!("<handle error here>");
    }
}

fn main() {
    *REAL_CALLBACK.lock().unwrap() = Some(Box::new(move |x, y| {
        println!("...");
        true
    }));
    unsafe {
        do_it(callback);
    }
}

It is also possible to create a trampoline function to stick the context directly in the function, but it is extremely difficult and unsafe. 也可以创建一个蹦床功能以将上下文直接保留在该功能中,但这是极其困难且不安全的。

Answer manually migrated from https://stackoverflow.com/a/42597209/224671 https://stackoverflow.com/a/42597209/224671手动迁移的答案

The first snippet from Vladimir Matveev no longer works as written. 弗拉基米尔·马特维耶夫(Vladimir Matveev)的第一个代码段不再按书​​面形式工作。 The size of &mut FnMut(i32) -> bool and *mut c_void is different and such casts lead to a crash. &mut FnMut(i32) -> bool*mut c_void大小不同,这样的强制转换会导致崩溃。 Corrected example ( playpen ): 更正的示例(游戏围栏 ):

extern crate libc;

use std::mem::*;

use libc::c_void;

pub fn run<F>(mut callback: F) -> bool
    where F: FnMut(i32) -> bool
{
    let mut cb: &mut FnMut(i32) -> bool = &mut callback;
    println!("sizeof(cb/*-ptr): {}/{}",
             size_of::<*mut FnMut(i32) -> bool>(),
             size_of::<*mut c_void>());

    let ctx = &mut cb as *mut &mut FnMut(i32) -> bool as *mut c_void;
    println!("ctx: {:?}", ctx);
    //----------------------------------------------------------
    // Convert backward
    let cb2: *mut *mut FnMut(i32) -> bool = unsafe { transmute(ctx) };
    println!("cb2: {:?}", cb2);

    // this is more useful, but can't be printed, because not implement Debug
    let closure: &mut &mut FnMut(i32) -> bool = unsafe { transmute(ctx) };

    closure(0xDEAD)
}

fn main() {
    println!("answer: {}",
             run(|x| {
                 println!("What can change nature of a man?");
                 x > 42
             }));
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM