Sure, here’s the translated part: 我可以使用Rust创建一个回调包装器吗?

huangapple go评论51阅读模式
英文:

Can I create a callback wrapper with Rust?

问题

I want to call the native Windows CopyFileEx function from within a Rust program but I am having trouble getting a working example of LPPROGRESS_ROUTINE. I am a Java programmer learning Rust and the lack of OOP paradigms is a challenge for me. In Java, I would use a class that implemented an interface as my callback. However, it looks like the lpprogressroutine parameter is a pointer to a function, rather than a polymorphic object. But this should be fine; I can just declare a function and create a pointer to it.

我想在Rust程序中调用本机的Windows CopyFileEx函数,但我在获取LPPROGRESS_ROUTINE的工作示例时遇到了问题。我是一名学习Rust的Java程序员,缺乏面向对象的编程范式对我来说是一个挑战。在Java中,我会使用实现接口的类作为我的回调。然而,看起来lpprogressroutine参数是一个指向函数的指针,而不是多态对象。但这应该没问题;我可以只需声明一个函数并创建一个指针。

I want the callback function to call a different function with additional arguments, so I created a wrapper struct to contain those additional arguments and the callback function:

我希望回调函数能够调用一个带有额外参数的不同函数,因此我创建了一个包装结构来包含这些额外参数和回调函数:

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

Now in order to access the env and ext_callback fields that I defined on the struct, I had to add the &mut self parameter to my invoke function. I think already this ruins the function signature so it wont work as a LPPROGRESS_ROUTINE, but perhaps not.

现在,为了访问我在结构体上定义的envext_callback字段,我不得不将&mut self参数添加到我的invoke函数中。我认为这已经破坏了函数签名,因此它可能无法作为LPPROGRESS_ROUTINE工作,但也许不是。

Continuing the endeavor, I create a method which will construct my Callback implementation and invoke the CopyFileExA function with a pointer to my method. This is where I am having trouble. I cannot figure out how to create a pointer to the callback method, since it is not static:

继续努力,我创建了一个方法,用于构建我的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,
        );
    }
}

I think I am just struggling from lack of experience working with this new language. Am I taking the correct approach using struct? Is there a better way to do this?

我认为我只是因为缺乏使用这种新语言的经验而感到困难。我是否采取了正确的方法使用struct?是否有更好的方法来做这个?

英文:

I want to call the native Windows CopyFileEx function from within a Rust program but I am having trouble getting a working example of LPPROGRESS_ROUTINE. I am a Java programmer learning Rust and the lack of OOP paradigms is a challenge for me. In Java, I would use a class that implemented an interface as my callback. However, it looks like the lpprogressroutine parameter is a pointer to a function, rather than a polymorphic object. But this should be fine; I can just declare a function and create a pointer to it.

I want the callback function to call a different function with additional arguments, so I created a wrapper struct to contain those additional arguments and the callback function:

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&lt;&#39;a&gt; {
    env: JNIEnv&lt;&#39;a&gt;,
    ext_callback: JObject&lt;&#39;a&gt;,
}

impl&lt;&#39;a&gt; Callback&lt;&#39;a&gt; {
    fn new(env: JNIEnv&lt;&#39;a&gt;, ext_callback: JObject&lt;&#39;a&gt;) -&gt; Self {
        Callback {
            env: env,
            ext_callback: ext_callback,
        }
    }

    unsafe extern &quot;system&quot; fn invoke(
        &amp;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,
    ) -&gt; u32 {
        let arr = [
            JValueGen::Long(totalfilesize),
            JValueGen::Long(totalbytestransferred),
            JValueGen::Long(streamsize),
            JValueGen::Long(streambytestransferred),
        ];
        self.env
            .call_method(&amp;self.callback, &quot;onProgressEvent&quot;, &quot;(IIII)V&quot;, &amp;arr)
            .expect(&quot;Java callback failed&quot;);
        return 0;
    }
}

Now in order to access the env and ext_callback fields that I defined on the struct, I had to add the &amp;mut self parameter to my invoke function. I think already this ruins the function signature so it wont work as a LPPROGRESS_ROUTINE, but perhaps not.

Continuing the endeavor, I create a method which will construct my Callback implementation and invoke the CopyFileExA function with a pointer to my method. This is where I am having trouble. I cannot figure out how to create a pointer to the callback method, since it is not static:

pub extern &quot;system&quot; fn Java_com_nhbb_util_natives_WindowsCopy_copy&lt;&#39;local&gt;(
    mut env: JNIEnv&lt;&#39;local&gt;,
    _object: JObject&lt;&#39;local&gt;,
    source: JString&lt;&#39;local&gt;,
    dest: JString&lt;&#39;local&gt;,
    flags: jint,
    ext_callback: JObject&lt;&#39;local&gt;,
) {
    let source_jstr: JavaStr = env.get_string(&amp;source).expect(&quot;Invalid source string&quot;);
    let dest_jstr: JavaStr = env.get_string(&amp;dest).expect(&quot;Invalid dest string&quot;);

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

I think I am just struggling from lack of experience working with this new language. Am I taking the correct approach using struct? Is there a better way to do this?

答案1

得分: 2

这是您提供的代码的中文翻译部分:

重要的是要理解的是,callback::invoke 不是 已绑定到 Callback 对象的普通函数。不存在隐式结构绑定;Callback::invoke 需要作为其第一个参数的 Callback 选项。这意味着它不会直接与 LPPROGRESS_ROUTINE 一起使用,也永远不会这样做。LPPROGRESS_ROUTINE 需要一个静态函数。

因此,您的解决方案是创建一个静态函数,通过其 lpdata 参数传递 callback 选项。这正是 lpdata 参数的用途。

请注意,由于 lpdata 参数在 C 中传递回来,它将是原始指针,您将需要使用 unsafe 来使用它。

这是演示如何工作的示例。请注意,我剥离了所有 JNI 相关的内容,但相同的原则也适用于 JNI。

请注意,PCSTR 的编码取决于区域设置,因此建议如前所示使用它。建议改用 PCWSTR,这是全局不含糊的。根据 Windows API 文档,PCWSTR 编码为 UTF-16LE。以下是使用 PCWSTR 的示例代码。

希望这有所帮助!

英文:

The important thing to understand here is that callback::invoke is not a normal function already bound to the Callback object. There is no such thing as an implicit struct binding; Callback::invoke requires the Callback option as its first argument. This means it does not and will never work with LPPROGRESS_ROUTINE directly. LPPROGRESS_ROUTINE expects a static function.

So your solution is to create a static function that gets the callback option passed through its lpdata argument. That's exactly what the lpdata argument is for.

Note that because the lpdata argument gets passed to C and back, it will be a raw pointer and you will require unsafe to use it.

Here is a demonstration of how this would work. Note that I stripped out all the JNI stuff, but the same principle should work with JNI as well.

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&lt;&#39;a&gt; {
    ext_callback: &amp;&#39;a mut dyn FnMut(i64, i64, i64, i64) -&gt; u32,
}

impl&lt;&#39;a&gt; Callback&lt;&#39;a&gt; {
    fn new(ext_callback: &amp;&#39;a mut dyn FnMut(i64, i64, i64, i64) -&gt; u32) -&gt; Self {
        Callback { ext_callback }
    }

    unsafe extern &quot;system&quot; 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,
    ) -&gt; 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!(
                &quot;Progress: File: {}/{}, Stream Size: {}, Stream bytes transferred: {}&quot;,
                totalbytestransferred, totalfilesize, streamsize, streambytestransferred,
            );
            0
        };

    let mut callback = Callback::new(&amp;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(&quot;file_a.txt&quot;).0.into_owned();
    let mut dest = WINDOWS_1252.encode(&quot;file_b.txt&quot;).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(((&amp;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

FURTHER REMARKS, IMPORANT:

The encoding of PCSTR depends on the locale, and should therefore not be used as shown previously. While most western locales do indeed use Windows-1252, many others do not.

It is therefore recommended to use PCWSTR instead, which is globally unambiguous.

According to the Windows API docs, a PCWSTR is encoded as UTF-16LE.

This is how the previous code would be rewritten for it:

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&lt;&#39;a&gt; {
    ext_callback: &amp;&#39;a mut dyn FnMut(i64, i64, i64, i64) -&gt; u32,
}

impl&lt;&#39;a&gt; Callback&lt;&#39;a&gt; {
    fn new(ext_callback: &amp;&#39;a mut dyn FnMut(i64, i64, i64, i64) -&gt; u32) -&gt; Self {
        Callback { ext_callback }
    }

    unsafe extern &quot;system&quot; 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,
    ) -&gt; 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!(
                &quot;Progress: File: {}/{}, Stream Size: {}, Stream bytes transferred: {}&quot;,
                totalbytestransferred, totalfilesize, streamsize, streambytestransferred,
            );
            0
        };

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

    // IMPORTANT: Rust strings are UTF-8, but `CopyFileExW`
    // requires UTF-16 with null termination.
    let source: Vec&lt;u16&gt; = &quot;file_&#128512;_a.txt&quot;
        .encode_utf16()
        .chain(iter::once(0)) // null termination
        .collect();
    let dest: Vec&lt;u16&gt; = &quot;file_&#128512;_b.txt&quot;
        .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(((&amp;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

huangapple
  • 本文由 发表于 2023年4月7日 04:12:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/75953404.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定