我可以用Rust创建回调 Package 器吗?

sh7euo9m  于 2023-04-12  发布在  其他
关注(0)|答案(1)|浏览(111)

我想从Rust程序中调用原生Windows CopyFileEx函数,但我很难得到LPPROGRESS_ROUTINE的工作示例。我是一名学习Rust的Java程序员,缺乏OOP范式对我来说是一个挑战。在Java中,我会使用一个实现接口的类作为回调函数。然而,看起来lpprogressroutine参数是一个指向函数的指针,而不是多态对象。但这应该没问题;我可以声明一个函数并创建一个指向它的指针。
我希望回调函数调用一个带有额外参数的不同函数,所以我创建了一个 Package 器结构来包含这些额外参数和回调函数:

use jni::objects::{JObject, JString, JValueGen};
use jni::strings::JavaStr;
use jni::sys::jint;
use jni::JNIEnv;
use windows::core::*;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Storage::FileSystem::{
    CopyFileExA, LPPROGRESS_ROUTINE, LPPROGRESS_ROUTINE_CALLBACK_REASON,
};

struct Callback<'a> {
    env: JNIEnv<'a>,
    ext_callback: JObject<'a>,
}

impl<'a> Callback<'a> {
    fn new(env: JNIEnv<'a>, ext_callback: JObject<'a>) -> Self {
        Callback {
            env: env,
            ext_callback: ext_callback,
        }
    }

    unsafe extern "system" fn invoke(
        &mut self,
        totalfilesize: i64,
        totalbytestransferred: i64,
        streamsize: i64,
        streambytestransferred: i64,
        _dwstreamnumber: u32,
        _dwcallbackreason: LPPROGRESS_ROUTINE_CALLBACK_REASON,
        _hsourcefile: HANDLE,
        _hdestinationfile: HANDLE,
        _lpdata: *const ::core::ffi::c_void,
    ) -> u32 {
        let arr = [
            JValueGen::Long(totalfilesize),
            JValueGen::Long(totalbytestransferred),
            JValueGen::Long(streamsize),
            JValueGen::Long(streambytestransferred),
        ];
        self.env
            .call_method(&self.callback, "onProgressEvent", "(IIII)V", &arr)
            .expect("Java callback failed");
        return 0;
    }
}

现在,为了访问我在结构体上定义的envext_callback字段,我必须将&mut self参数添加到invoke函数中。我认为这已经破坏了函数签名,因此它不会作为LPPROGRESS_ROUTINE工作,但也许不会。
继续奋进,我创建了一个方法,它将构造我的Callback实现,并使用指向我的方法的指针调用CopyFileExA函数。这就是我遇到麻烦的地方。我不知道如何创建指向回调方法的指针,因为它不是静态的:

pub extern "system" fn Java_com_nhbb_util_natives_WindowsCopy_copy<'local>(
    mut env: JNIEnv<'local>,
    _object: JObject<'local>,
    source: JString<'local>,
    dest: JString<'local>,
    flags: jint,
    ext_callback: JObject<'local>,
) {
    let source_jstr: JavaStr = env.get_string(&source).expect("Invalid source string");
    let dest_jstr: JavaStr = env.get_string(&dest).expect("Invalid dest string");

    let source_arr = source_jstr.get_raw();
    let dest_arr = dest_jstr.get_raw();

    let source = source_arr as *const u8;
    let dest = dest_arr as *const u8;

    let flags: u32 = flags.try_into().unwrap();

    let callback = Callback::new(env, ext_callback);

    unsafe {
        CopyFileExA(
            PCSTR(source),
            PCSTR(dest),
            LPPROGRESS_ROUTINE::Some(callback::invoke),
    //                               ^^^^^^^^ use of undeclared crate or module `callback`
            None,
            None,
            flags,
        );
    }
}

我想我只是因为缺乏使用这种新语言的经验而挣扎。我使用struct的方法正确吗?有更好的方法吗?

dxxyhpgq

dxxyhpgq1#

这里需要理解的重要一点是,callback::invoke不是一个已经绑定到Callback对象的普通函数。Callback::invoke需要Callback选项作为它的第一个参数。这意味着它不会也永远不会直接与LPPROGRESS_ROUTINE一起工作。LPPROGRESS_ROUTINE需要静态函数。
所以你的解决方案是创建一个静态函数,通过它的lpdata参数传递callback选项。这正是lpdata参数的作用。
请注意,因为lpdata参数传递给C并返回,所以它将是一个原始指针,您将需要unsafe来使用它。
下面是一个如何工作的演示。注意,我去掉了所有JNI的东西,但同样的原理也适用于JNI。

use std::ffi::c_void;

use encoding_rs::WINDOWS_1252;
use windows::core::*;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Storage::FileSystem::{
    CopyFileExA, LPPROGRESS_ROUTINE, LPPROGRESS_ROUTINE_CALLBACK_REASON,
};

struct Callback<'a> {
    ext_callback: &'a mut dyn FnMut(i64, i64, i64, i64) -> u32,
}

impl<'a> Callback<'a> {
    fn new(ext_callback: &'a mut dyn FnMut(i64, i64, i64, i64) -> u32) -> Self {
        Callback { ext_callback }
    }

    unsafe extern "system" fn invoke(
        totalfilesize: i64,
        totalbytestransferred: i64,
        streamsize: i64,
        streambytestransferred: i64,
        _dwstreamnumber: u32,
        _dwcallbackreason: LPPROGRESS_ROUTINE_CALLBACK_REASON,
        _hsourcefile: HANDLE,
        _hdestinationfile: HANDLE,
        lpdata: *const c_void,
    ) -> u32 {
        let this_ptr: *mut Self = lpdata.cast_mut().cast();
        let this = this_ptr.as_mut().unwrap();

        (this.ext_callback)(
            totalfilesize,
            totalbytestransferred,
            streamsize,
            streambytestransferred,
        )
    }
}

fn main() {
    let mut ext_callback =
        |totalfilesize, totalbytestransferred, streamsize, streambytestransferred| {
            println!(
                "Progress: File: {}/{}, Stream Size: {}, Stream bytes transferred: {}",
                totalbytestransferred, totalfilesize, streamsize, streambytestransferred,
            );
            0
        };

    let mut callback = Callback::new(&mut ext_callback);

    // IMPORTANT: Rust strings are UTF-8, but `CopyFileExA` requires an ANSI/Windows-1252 string!
    // Use `encoding_rs` to convert between the two.
    let mut source = WINDOWS_1252.encode("file_a.txt").0.into_owned();
    let mut dest = WINDOWS_1252.encode("file_b.txt").0.into_owned();
    // Add null termination
    source.push(0);
    dest.push(0);

    unsafe {
        CopyFileExA(
            PCSTR::from_raw(source.as_ptr()),
            PCSTR::from_raw(dest.as_ptr()),
            LPPROGRESS_ROUTINE::Some(Callback::invoke),
            Some(((&mut callback) as *mut Callback).cast()),
            None,
            0,
        );
    }
}
Progress: File: 0/23, Stream Size: 23, Stream bytes transferred: 0
Progress: File: 23/23, Stream Size: 23, Stream bytes transferred: 23

其他备注,重要说明:

PCSTR的编码取决于语言环境,因此不应该像前面所示的那样使用**。虽然大多数西方语言环境确实使用Windows-1252,但许多其他语言环境并不使用。
因此,建议使用PCWSTR,这是全局明确的。
根据Windows API文档,PCWSTR被编码为UTF-16LE
这是前面的代码将如何重写它:

use std::ffi::c_void;
use std::iter;

use windows::core::*;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Storage::FileSystem::{
    CopyFileExW, LPPROGRESS_ROUTINE, LPPROGRESS_ROUTINE_CALLBACK_REASON,
};

struct Callback<'a> {
    ext_callback: &'a mut dyn FnMut(i64, i64, i64, i64) -> u32,
}

impl<'a> Callback<'a> {
    fn new(ext_callback: &'a mut dyn FnMut(i64, i64, i64, i64) -> u32) -> Self {
        Callback { ext_callback }
    }

    unsafe extern "system" fn invoke(
        totalfilesize: i64,
        totalbytestransferred: i64,
        streamsize: i64,
        streambytestransferred: i64,
        _dwstreamnumber: u32,
        _dwcallbackreason: LPPROGRESS_ROUTINE_CALLBACK_REASON,
        _hsourcefile: HANDLE,
        _hdestinationfile: HANDLE,
        lpdata: *const c_void,
    ) -> u32 {
        let this_ptr: *mut Self = lpdata.cast_mut().cast();
        let this = this_ptr.as_mut().unwrap();

        (this.ext_callback)(
            totalfilesize,
            totalbytestransferred,
            streamsize,
            streambytestransferred,
        )
    }
}

fn main() {
    let mut ext_callback =
        |totalfilesize, totalbytestransferred, streamsize, streambytestransferred| {
            println!(
                "Progress: File: {}/{}, Stream Size: {}, Stream bytes transferred: {}",
                totalbytestransferred, totalfilesize, streamsize, streambytestransferred,
            );
            0
        };

    let mut callback = Callback::new(&mut ext_callback);

    // IMPORTANT: Rust strings are UTF-8, but `CopyFileExW`
    // requires UTF-16 with null termination.
    let source: Vec<u16> = "file_😀_a.txt"
        .encode_utf16()
        .chain(iter::once(0)) // null termination
        .collect();
    let dest: Vec<u16> = "file_😀_b.txt"
        .encode_utf16()
        .chain(iter::once(0)) // null termination
        .collect();

    unsafe {
        CopyFileExW(
            PCWSTR::from_raw(source.as_ptr()),
            PCWSTR::from_raw(dest.as_ptr()),
            LPPROGRESS_ROUTINE::Some(Callback::invoke),
            Some(((&mut callback) as *mut Callback).cast()),
            None,
            0,
        );
    }
}
Progress: File: 0/30, Stream Size: 30, Stream bytes transferred: 0
Progress: File: 30/30, Stream Size: 30, Stream bytes transferred: 30

相关问题