Rust 如何通过原始指针将闭包作为参数传递给C函数?

Rust 如何通过原始指针将闭包作为参数传递给C函数?,rust,closures,traits,ffi,Rust,Closures,Traits,Ffi,我在Rust中使用WinAPI,有些函数(如)需要回调。回调通常接受一个附加参数(类型为LPARAM,它是i64的别名),您可以使用它将一些自定义数据传递给回调 我已经将Vec对象作为LPARAM发送到WinAPI回调,效果很好。例如,在我的例子中,将lparam值“解包”为Vec,如下所示: unsafe extern "system" fn enumerate_callback(hwnd: HWND, lparam: LPARAM) -> BOOL { let rects =

我在Rust中使用WinAPI,有些函数(如)需要回调。回调通常接受一个附加参数(类型为
LPARAM
,它是
i64
的别名),您可以使用它将一些自定义数据传递给回调

我已经将
Vec
对象作为LPARAM发送到WinAPI回调,效果很好。例如,在我的例子中,将
lparam
值“解包”为
Vec
,如下所示:

unsafe extern "system" fn enumerate_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
    let rects = lparam as *mut Vec<RECT>;
}
SSCCE:

use std::os::raw::c_void;

fn enum_wnd_proc(some_value: i32, lparam: i32) {
    let closure: &mut FnMut(i32) -> bool =
        unsafe { (&mut *(lparam as *mut c_void as *mut FnMut(i32) -> bool)) };

    println!("predicate() executed and returned: {}", closure(some_value));
}

fn main() {
    let sum = 0;
    let mut closure = |some_value: i32| -> bool {
        sum += some_value;
        sum >= 100
    };

    let lparam = (&mut closure as *mut c_void as *mut FnMut(i32) -> bool) as i32;
    enum_wnd_proc(20, lparam);
}
()

我发现以下错误:

错误[E0277]:应为'std::ops::FnMut'闭包,发现'std::ffi::c_void'`
-->src/main.rs:5:26
|
5 |不安全{(&mut*(lparam作为*mut c_void作为*mut FnMut(i32)->bool));
|^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^`
|
=help:trait`std::ops::FnMut`未为`std::ffi::c_void'实现`
=注意:强制转换到对象类型'dyn std::ops::FnMut(i32)->bool时需要`
错误[E0606]:强制转换“%mut”[closure@src/main.rs:12:23:15:6 sum:]`as`*mut std::ffi::c_void`无效
-->src/main.rs:17:19
|
17 |设lparam=(&mut闭包为*mut c_void为*mut FnMut(i32)->bool)为i32;
|                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^
错误[E0606]:将`*mut-dyn std::ops::FnMut(i32)->bool`转换为`i32`无效
-->src/main.rs:17:18
|
17 |设lparam=(&mut闭包为*mut c_void为*mut FnMut(i32)->bool)为i32;
|                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
=帮助:首先通过细指针强制转换
错误[E0277]:应为'std::ops::FnMut'闭包,发现'std::ffi::c_void'`
-->src/main.rs:17:19
|
17 |设lparam=(&mut闭包为*mut c_void为*mut FnMut(i32)->bool)为i32;
|^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^`
|
=help:trait`std::ops::FnMut`未为`std::ffi::c_void'实现`
=注意:强制转换到对象类型'dyn std::ops::FnMut(i32)->bool时需要`
我想知道:

  • 有没有办法将函数/闭包传递给另一个函数并执行那些“类似C”的强制转换
  • i64
    值强制转换闭包以将其传递给回调的正确方法是什么

  • 我使用的是Rust的稳定版本。

    首先,代码中存在一些逻辑错误:

  • 在许多平台(如64位平台)上,将指针投射到
    i32
    是不正确的。指针可以使用所有这些位。截断一个指针,然后在被截断的地址调用一个函数会导致非常糟糕的事情。通常,您希望使用机器大小的整数(
    usize
    isize

  • sum
    值需要是可变的

  • 问题的实质是闭包是具体类型,其大小程序员不知道,但编译器知道。C函数仅限于获取机器大小的整数

    因为闭包实现了一个
    Fn*
    特征,所以我们可以引用闭包对该特征的实现来生成一个特征对象。引用一个trait会导致一个包含两个指针大小值的胖指针。在本例中,它包含一个指向关闭的数据的指针和一个指向vtable的指针,vtable是实现trait的具体方法

    一般来说,对的任何引用或
    都将生成一个fat指针

    在64位机器上,胖指针总共是128位,将其转换为机器大小的指针将再次截断数据,从而导致真正糟糕的事情发生

    与计算机科学中的所有其他方法一样,解决方案是添加更多的抽象层:

    use std::os::raw::c_void;
    
    fn enum_wnd_proc(some_value: i32, lparam: usize) {
        let trait_obj_ref: &mut &mut FnMut(i32) -> bool = unsafe {
            let closure_pointer_pointer = lparam as *mut c_void;
            &mut *(closure_pointer_pointer as *mut _)
        };
        println!(
            "predicate() executed and returned: {}",
            trait_obj_ref(some_value)
        );
    }
    
    fn main() {
        let mut sum = 0;
        let mut closure = |some_value: i32| -> bool {
            println!("I'm summing {} + {}", sum, some_value);
            sum += some_value;
            sum >= 100
        };
    
        let mut trait_obj: &mut FnMut(i32) -> bool = &mut closure;
        let trait_obj_ref = &mut trait_obj;
    
        let closure_pointer_pointer = trait_obj_ref as *mut _ as *mut c_void;
        let lparam = closure_pointer_pointer as usize;
    
        enum_wnd_proc(20, lparam);
    }
    
    我们对胖指针进行第二次引用,这将创建一个瘦指针。此指针的大小仅为一个机器整数

    也许一张图表会有帮助(或伤害)

    Reference->Trait object->Concrete closure
    8字节16字节??字节
    
    因为我们使用的是原始指针,所以程序员现在的责任是确保闭包在使用它的地方仍然有效!如果
    enum\u wnd\u proc
    将指针存储在某处,则必须非常小心,不要在删除闭包后使用它


    作为旁注,在铸造trait对象时使用
    mem::transmute

    use std::mem;
    let closure_pointer_pointer: *mut c_void = unsafe { mem::transmute(trait_obj) };
    
    生成更好的错误消息:

    error[E0512]:使用不同大小的类型调用transmute
    -->src/main.rs:26:57
    |
    26 | let closure_pointer_pointer:*mut c_void=不安全{mem::transmute(trait_obj)};
    |                                                         ^^^^^^^^^^^^^^
    |
    =注意:源类型:&mutdyn std::ops::FnMut(i32)->bool(128位)
    =注:目标类型:*mut std::ffi::c_void(64位)
    


    另见


    感谢您的详细回答。我完全同意你关于
    i32
    sum
    的观点,我不知道为什么我把
    i32
    放在我的示例代码中,实际值的类型是
    i64
    (我最初也提到过,但不知何故忘了在代码中更改它)。唯一一件事我仍然无法理解,为什么我们要把指针指向指针?即使closura/trait是无大小的对象,并且由两个指针组成,它仍然是一个有两个指针的结构,对吗?那么,为什么我们不能将指针指向存储该对象的内存位置并使用它呢?(就像在C语言中,你可以使用指向结构的指针,或者C++中指向类的指针)@ScienceSE这就是基本上正在发生的事情。看看我的重写/图表是否有帮助?那么我做对了吗?在Rust中,当你试图将指针指向一个未大小的类型时,它会给你一个所谓的“胖p”
    use std::mem;
    let closure_pointer_pointer: *mut c_void = unsafe { mem::transmute(trait_obj) };