首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何将锈蚀闭包转换为C风格的回调?

如何将锈蚀闭包转换为C风格的回调?
EN

Stack Overflow用户
提问于 2015-08-28 11:27:55
回答 2查看 9.7K关注 0票数 30

我正试图为一段C编写一个生锈的包装器。有一个C结构我很难理解:

代码语言:javascript
运行
复制
typedef bool (*listener_t) (int, int);
bool do_it(int x1, int y1, int x2, int y2, listener_t listener)

除非侦听器返回false,否则该函数将对一系列数字执行其任务。在这种情况下,它将中止计算。我想要一个这样的锈蚀包装纸:

代码语言:javascript
运行
复制
fn do_with_callback<F>(start: (i32, i32), end: (i32, i32), callback: F)
    where F: Fn(i32, i32) -> bool

rust-bindgen为我创建了这个页面,为了清晰起见对其进行了稍微的编辑:

代码语言:javascript
运行
复制
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样式回调:

代码语言:javascript
运行
复制
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))
    };
}
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2015-08-28 11:38:49

除非C允许传递用户提供的回调参数,否则不能这样做。如果没有,则只能使用静态函数。

原因是闭包不是“只是”函数。正如它们的名称所暗示的那样,闭包从其词法范围“关闭”变量。每个闭包都有一个相关的数据段,它包含捕获变量的值(如果使用move关键字)或对它们的引用。这些数据可以被认为是一些未命名的匿名struct

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

如果您的C API只允许在没有任何用户定义参数的情况下传递函数,则不能编写允许使用闭包的包装器。我想为闭包环境编写一些全局持有者可能是可能的,但我怀疑这是否简单和安全。

如果您的C确实允许传递一个用户定义的参数,那么就可以使用属性对象来做您想做的事情:

代码语言:javascript
运行
复制
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(..) -> ..>属性对象,并在将它传递给函数之后泄漏它。然后,如果可能的话,它应该从你的C库中获得并处理掉。看起来可能是这样的:

代码语言:javascript
运行
复制
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不兼容。

票数 40
EN

Stack Overflow用户

发布于 2017-03-03 20:24:53

弗拉基米尔·马特维耶夫的第一个片段不再像编写的那样工作了。&mut FnMut(i32) -> bool*mut c_void的大小是不同的,这样的转换会导致崩溃。校正示例(游戏笔):

代码语言:javascript
运行
复制
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
             }));
}
票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32270030

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档