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

问题描述 投票:4回答:1

我在锈WinAPI的工作,有需要回调的某些功能(如EnumWindows())。回调通常接受一个额外的参数,其可用于某些自定义数据传递给回调(类型LPARAM其是用于i64的别名)。

我已经派Vec<T>对象作为LPARAM到WinAPI的回调和它工作得很好。例如“拆包”的lparam值到Vec<RECT>看起来像这样在我的情况:

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

而是通过一个载体,我现在必须通过关闭。我不能用一个函数指针作为我的封盖具有捕捉到一些变量,如果我用一个函数,将不可访问。在C ++中,我会用std::function<>我特定的任务,我认为,在锈相应的抽象是一个封闭。

我对拆包代码如下所示:

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

(Qazxswpoi)

我得到这些错误:

Playground

我想知道:

  1. 有没有办法给一个函数/闭包传递到不同的功能,并执行那些“C类”铸就?
  2. 什么是投一个封闭的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` 值将它传递给该回调的正确方法?

我使用防锈的稳定版本。

rust closures traits ffi
1个回答
10
投票

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

  1. 这是不正确的投三分球,以i64在许多平台上(如64位)。指针可以使用所有这些位。截断指针,然后调用在被截断的地址的功能会导致非常糟糕的事情。通常你想用一台机器尺寸整数(i32usize)。
  2. isize值必须是可变的。

这个问题的肉是闭包是占用大小未知的程序员的具体类型,但是编译器知道的。 C函数被限制为采取机尺寸的整数。

由于封落实sum特征之一,我们可以采取封闭的实现,特性的参考来生成一个特质对象。以一参一特质导致了包含两个指针大小值的脂肪指针。在这种情况下,它包含一个指向被关闭,在数据和一个指向虚函数表,实现了性状的具体方法。

通常,Fn*的任何引用或Box将要产生一个脂肪指针。

在64位机,脂肪指针将总共128位,和流延到机器大小的指针将再次截断数据,导致非常糟糕的事情发生。

该解决方案一样,一切都在计算机科学别的,就是增加一些抽象层以上:

dynamically-sized type type

我们采取一个第二参考脂肪指针,它创建了一个薄的指针。这个指针是只有一台机器整数的大小。

也许图将有助于(或伤害)?

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 bytes 16 bytes ?? bytes 某处存储指针,你必须非常小心,不要使用它关闭删除后。


作为一个侧面说明,使用enum_wnd_proc铸造性状对象时:

mem::transmute

产生更好的错误信息:

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)


也可以看看

© www.soinside.com 2019 - 2024. All rights reserved.