Rust 如何通过原始指针将闭包作为参数传递给C函数?
我在Rust中使用WinAPI,有些函数(如)需要回调。回调通常接受一个附加参数(类型为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 =
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时需要`
我想知道:
i64
值强制转换闭包以将其传递给回调的正确方法是什么我使用的是Rust的稳定版本。首先,代码中存在一些逻辑错误:
i32
是不正确的。指针可以使用所有这些位。截断一个指针,然后在被截断的地址调用一个函数会导致非常糟糕的事情。通常,您希望使用机器大小的整数(usize
或isize
)sum
值需要是可变的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) };