如何通过原始指针将闭包作为参数传递给 C 函数? [英] How do I pass a closure through raw pointers as an argument to a C function?

查看:26
本文介绍了如何通过原始指针将闭包作为参数传递给 C 函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在 Rust 中使用 WinAPI,并且有一些功能(例如 EnumWindows()) 需要回调.回调通常接受一个额外的参数(LPARAM 类型,它是 i64 的别名),您可以使用它向回调传递一些自定义数据.

I'm working with WinAPI in Rust and there are some functions (like EnumWindows()) which require a callback. The callback usually accepts an additional argument (of type LPARAM which is an alias for i64), which you can use to pass some custom data to the callback.

我已将 Vec 对象作为 LPARAM 发送到 WinAPI 回调,并且运行良好.例如,在我的情况下,将 lparam 值解包"到 Vec 看起来像这样:

I have sent Vec<T> objects as LPARAM to the WinAPI callbacks and it worked fine. For instance "unpacking" an lparam value to a Vec<RECT> looked like this in my case:

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

我现在必须传递一个闭包而不是传递一个向量.我不能使用函数指针,因为我的闭包必须捕获一些变量,如果我使用函数,这些变量将无法访问.在 C++ 中,我会使用 std::function<> 来完成我的特定任务,我认为在 Rust 中相应的抽象是一个闭包.

Instead of passing a vector, I now have to pass a closure. I can not use a function pointer as my closure has to capture some variables, which would not be accessible if I used a function. In C++, I would use std::function<> for my particular task, I think that in Rust the corresponding abstraction is a closure.

我的解包代码如下所示:

My code for unpacking looks like this:

unsafe extern "system" fn enumerate_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
    let cb: &mut FnMut(HWND) -> bool = &mut *(lparam as *mut c_void as *mut FnMut(HWND) -> bool);
    // ...
}

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

(游乐场)

我收到这些错误:

error[E0277]: expected a `std::ops::FnMut<(i32,)>` closure, found `std::ffi::c_void`
 --> src/main.rs:5:26
  |
5 |         unsafe { (&mut *(lparam as *mut c_void as *mut FnMut(i32) -> bool)) };
  |                          ^^^^^^^^^^^^^^^^^^^^^ expected an `FnMut<(i32,)>` closure, found `std::ffi::c_void`
  |
  = help: the trait `std::ops::FnMut<(i32,)>` is not implemented for `std::ffi::c_void`
  = note: required for the cast to the object type `dyn std::ops::FnMut(i32) -> bool`

error[E0606]: casting `&mut [closure@src/main.rs:12:23: 15:6 sum:_]` as `*mut std::ffi::c_void` is invalid
  --> src/main.rs:17:19
   |
17 |     let lparam = (&mut closure as *mut c_void as *mut FnMut(i32) -> bool) as i32;
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0606]: casting `*mut dyn std::ops::FnMut(i32) -> bool` as `i32` is invalid
  --> src/main.rs:17:18
   |
17 |     let lparam = (&mut closure as *mut c_void as *mut FnMut(i32) -> bool) as i32;
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: cast through a thin pointer first

error[E0277]: expected a `std::ops::FnMut<(i32,)>` closure, found `std::ffi::c_void`
  --> src/main.rs:17:19
   |
17 |     let lparam = (&mut closure as *mut c_void as *mut FnMut(i32) -> bool) as i32;
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an `FnMut<(i32,)>` closure, found `std::ffi::c_void`
   |
   = help: the trait `std::ops::FnMut<(i32,)>` is not implemented for `std::ffi::c_void`
   = note: required for the cast to the object type `dyn std::ops::FnMut(i32) -> bool`

我想知道:

  1. 有没有办法将函数/闭包传递给不同的函数并执行那些类似 C"的强制转换?
  2. 将闭包转换为 i64 值以将其传递给该回调的正确方法是什么?
  1. Is there a way to pass a function/closure to a different function and perform those "C-like" casts?
  2. What is the proper way to cast a closure to a i64 value for passing it to that callback?

我使用的是稳定版的 Rust.

I'm using the stable version of Rust.

推荐答案

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

First, some logical errors with the code:

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

  1. It is not correct to cast pointers to i32 on many platforms (like 64-bit). Pointers may use all of those bits. Truncating a pointer and then calling a function at the truncated address will lead to Really Bad Things. Generally you want to use a machine-sized integer (usize or isize).

sum 值需要是可变的.

问题的核心在于闭包是占用程序员未知但编译器已知大小的具体类型.C 函数仅限于采用机器大小的整数.

The meat of the problem is that closures are concrete types that take up a size unknown to the programmer, but known to the compiler. The C function is limited to taking a machine-sized integer.

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

Because closures implement one of the Fn* traits, we can take a reference to the closure's implementation of that trait to generate a trait object. Taking a reference a trait leads to a fat pointer that contains two pointer-sized values. In this case, it contains a pointer to the data that is closed-over and a pointer to a vtable, the concrete methods that implement the trait.

一般来说,任何对 动态大小的类型 将生成一个胖指针.

In general, any reference to or Box of a dynamically-sized type type is going to generate a fat pointer.

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

On a 64-bit machine, a fat pointer would be 128 bits in total, and casting that to a machine-sized pointer would again truncate the data, causing Really Bad Things to happen.

解决方案,就像计算机科学中的其他一切一样,是添加更多抽象层:

The solution, like everything else in computer science, is to add more layers of abstraction:

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

我们再次引用胖指针,它创建了一个瘦指针.这个指针的大小只有一个机器整数.

We take a second reference to the fat pointer, which creates a thin pointer. This pointer is only one machine-integer in size.

也许图表会有所帮助(或伤害)?

Maybe a diagram will help (or hurt)?

Reference -> Trait object -> Concrete closure
 8 bytes       16 bytes         ?? bytes

因为我们使用的是原始指针,所以现在程序员有责任确保闭包在它被使用的地方持续存在!如果enum_wnd_proc 将指针存储在某处,则必须非常小心,不要在关闭闭包后使用它.

Because we are using raw pointers, it is now the programmers responsibility to make sure that the closure outlives where it is used! If enum_wnd_proc stores the pointer somewhere, you must be very careful to not use it after the closure is dropped.

作为旁注,在转换 trait 对象时使用 mem::transmute:

As a side note, using mem::transmute when casting the trait object:

use std::mem;
let closure_pointer_pointer: *mut c_void = unsafe { mem::transmute(trait_obj) };

产生更好的错误信息:

error[E0512]: transmute called with types of different sizes
  --> src/main.rs:26:57
   |
26 |     let closure_pointer_pointer: *mut c_void = unsafe { mem::transmute(trait_obj) };
   |                                                         ^^^^^^^^^^^^^^
   |
   = note: source type: &mut dyn std::ops::FnMut(i32) -> bool (128 bits)
   = note: target type: *mut std::ffi::c_void (64 bits)

错误 E0512.

另见

这篇关于如何通过原始指针将闭包作为参数传递给 C 函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆