如何将Rust闭包转换为C风格回调?

如何将Rust闭包转换为C风格回调?,rust,closures,ffi,Rust,Closures,Ffi,我正试图为一段C API编写一个生锈的包装。有一个C结构我很挣扎: typedef bool (*listener_t) (int, int); bool do_it(int x1, int y1, int x2, int y2, listener_t listener) 除非侦听器返回false,否则该函数对一系列数字执行其工作。在这种情况下,它会中止计算。我想要一个像这样的防锈纸: fn do_with_callback<F>(start: (i32, i32), end: (

我正试图为一段C API编写一个生锈的包装。有一个C结构我很挣扎:

typedef bool (*listener_t) (int, int);
bool do_it(int x1, int y1, int x2, int y2, listener_t listener)
除非侦听器返回false,否则该函数对一系列数字执行其工作。在这种情况下,它会中止计算。我想要一个像这样的防锈纸:

fn do_with_callback<F>(start: (i32, i32), end: (i32, i32), callback: F)
    where F: Fn(i32, i32) -> bool
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();
}
在我的
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))
    };
}
pub fn do_with_callback(开始:(i32,i32),结束:(i32,i32),callback:F)->Self
其中F:Fn(i32,i32)->bool
{
设包装器=???;
不安全{
ffi::dou_it(start.0、start.1、end.0、end.1、Some(包装器))
};
}

除非C API允许传递用户提供的回调参数,否则无法执行此操作。如果没有,则只能使用静态函数

原因是闭包不是“仅仅”函数。顾名思义,闭包从词法范围“关闭”变量。每个闭包都有一个关联的数据段,该数据段保存捕获变量的值(如果使用了
move
关键字)或对它们的引用。这些数据可以看作是一些未命名的、匿名的
struct

编译器会自动为这些匿名结构添加相应的
Fn*
特征的实现,除了闭包参数外,这些特性上的方法还接受
self
。在此上下文中,
self
是实现特征的
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 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 }
}
双间接寻址(即
Box
)是必要的,因为
Box..>
是一个特征对象,因此是一个胖指针,由于大小不同,与
*mut c_void
不兼容。

不再像写的那样工作。
&mut FnMut(i32)->bool
*mut c_void
的大小不同,这样的强制转换会导致崩溃。更正的示例():

extern板条箱libc;
使用std::mem::*;
使用libc::c_void;
pub fn run(mut回调:F)->bool
其中F:FnMut(i32)->bool
{
让mut cb:&mut FnMut(i32)->bool=&mut回调;
println!(“sizeof(cb/*-ptr):{}/{}”,
大小:bool>(),
尺寸(单位::());
设ctx=&mut cb as*mut&mut FnMut(i32)->bool as*mut c_void;
println!(“ctx:{:?}”,ctx);
//----------------------------------------------------------
//倒转
设cb2:*mut*mut-FnMut(i32)->bool=unsafe{transmute(ctx)};
println!(“cb2:{:?}”,cb2);
//这更有用,但无法打印,因为无法实现调试
let闭包:&mut&mut-FnMut(i32)->bool=unsafe{transmute(ctx)};
关闭(0xDEAD)
}
fn main(){
println!(“答案:{}),
运行(| x |{
什么能改变一个人的本性;
x>42
}));
}

在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)
。。。或者使用API来存储用户数据

void api_set_user_data(void* user_data);   // <-- caller set the context
void* api_get_user_data();   // <-- callback use this to retrieve context.
void api\u set\u user\u数据(void*user\u数据);//布尔{
如果让一些(ref mut real_callback)=*real_callback.lock().unwrap(){
实函数回调(x,y)
}否则{
恐慌!(“”);
}
}
fn main(){
*REAL|CALLBACK.lock().unwrap()=Some(Box::new(move | x,y |{
println!(“…”);
真的
}));
不安全{
做它(回调);
}
}
也可以创建一个直接将上下文粘贴到函数中的方法,但这是非常困难和不安全的


答案从

我明白了。不幸的是,此特定函数不允许传递任何用户数据。因此,为了让它工作,我必须要求用户提供一个
extern“C”
函数作为我的防锈包装器的参数?或者有没有一种方法可以使用trait/object?不幸的是,除了传递
extern“C”
函数,我看不到其他方法。也许其他人可以提出一些建议,但不太可能。@Vladimitveev我也有类似的要求。在我的例子中,userdata被传递到callback,但它是使用如下函数设置的-->
bindings::mosquitto\u user\u data\u set
。由于这个原因,当调用回调时,我的闭包就被破坏了,并且我在闭包变量上得到了垃圾。你能用你建议的
Box
方法的一个例子来扩展答案吗?@tez我已经添加了一个例子。@vladimirtmaveveve非常感谢你。我对此有点困惑。我问了一个新问题来解释我的问题。你能看看这个吗。这个库的C客户端如何传递特定于调用方的信息?这似乎是一个API示例,它的设计根本不允许这样做。也许API作者认为它不是必需的,并且您可以根据
(x,y)
做出所有需要的决定。它在很大程度上依赖于静态和全局状态。另外一个答案中的两个代码段都可以正确编译。你是说程序执行时会崩溃吗?是的。只要在play.rust-lang.org上试试。这个站点没有报告崩溃,只是一些
println
ed字符串没有打印,比如说我,应用程序崩溃了。在创建指针和获取闭包的过程中使用了不同的数量
&mut
,这已经是一个事实
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)
void api_set_user_data(void* user_data);   // <-- caller set the context
void* api_get_user_data();   // <-- callback use this to retrieve context.
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);
    }
}