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

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

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:

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

  1. use jni::objects::{JObject, JString, JValueGen};
  2. use jni::strings::JavaStr;
  3. use jni::sys::jint;
  4. use jni::JNIEnv;
  5. use windows::core::*;
  6. use windows::Win32::Foundation::HANDLE;
  7. use windows::Win32::Storage::FileSystem::{
  8. CopyFileExA, LPPROGRESS_ROUTINE, LPPROGRESS_ROUTINE_CALLBACK_REASON,
  9. };
  10. struct Callback<'a> {
  11. env: JNIEnv<'a>,
  12. ext_callback: JObject<'a>,
  13. }
  14. impl<'a> Callback<'a> {
  15. fn new(env: JNIEnv<'a>, ext_callback: JObject<'a>) -> Self {
  16. Callback {
  17. env: env,
  18. ext_callback: ext_callback,
  19. }
  20. }
  21. unsafe extern "system" fn invoke(
  22. &mut self,
  23. totalfilesize: i64,
  24. totalbytestransferred: i64,
  25. streamsize: i64,
  26. streambytestransferred: i64,
  27. _dwstreamnumber: u32,
  28. _dwcallbackreason: LPPROGRESS_ROUTINE_CALLBACK_REASON,
  29. _hsourcefile: HANDLE,
  30. _hdestinationfile: HANDLE,
  31. _lpdata: *const ::core::ffi::c_void,
  32. ) -> u32 {
  33. let arr = [
  34. JValueGen::Long(totalfilesize),
  35. JValueGen::Long(totalbytestransferred),
  36. JValueGen::Long(streamsize),
  37. JValueGen::Long(streambytestransferred),
  38. ];
  39. self.env
  40. .call_method(&self.callback, "onProgressEvent", "(IIII)V", &arr)
  41. .expect("Java callback failed");
  42. return 0;
  43. }
  44. }

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函数。这是我遇到困难的地方。我无法弄清如何创建回调方法的指针,因为它不是静态的:

  1. pub extern "system" fn Java_com_nhbb_util_natives_WindowsCopy_copy<'local>(
  2. mut env: JNIEnv<'local>,
  3. _object: JObject<'local>,
  4. source: JString<'local>,
  5. dest: JString<'local>,
  6. flags: jint,
  7. ext_callback: JObject<'local>,
  8. ) {
  9. let source_jstr: JavaStr = env.get_string(&source).expect("Invalid source string");
  10. let dest_jstr: JavaStr = env.get_string(&dest).expect("Invalid dest string");
  11. let source_arr = source_jstr.get_raw();
  12. let dest_arr = dest_jstr.get_raw();
  13. let source = source_arr as *const u8;
  14. let dest = dest_arr as *const u8;
  15. let flags: u32 = flags.try_into().unwrap();
  16. let callback = Callback::new(env, ext_callback);
  17. unsafe {
  18. CopyFileExA(
  19. PCSTR(source),
  20. PCSTR(dest),
  21. LPPROGRESS_ROUTINE::Some(callback::invoke),
  22. // ^^^^^^^^ use of undeclared crate or module `callback`
  23. None,
  24. None,
  25. flags,
  26. );
  27. }
  28. }

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:

  1. use jni::objects::{JObject, JString, JValueGen};
  2. use jni::strings::JavaStr;
  3. use jni::sys::jint;
  4. use jni::JNIEnv;
  5. use windows::core::*;
  6. use windows::Win32::Foundation::HANDLE;
  7. use windows::Win32::Storage::FileSystem::{
  8. CopyFileExA, LPPROGRESS_ROUTINE, LPPROGRESS_ROUTINE_CALLBACK_REASON,
  9. };
  10. struct Callback&lt;&#39;a&gt; {
  11. env: JNIEnv&lt;&#39;a&gt;,
  12. ext_callback: JObject&lt;&#39;a&gt;,
  13. }
  14. impl&lt;&#39;a&gt; Callback&lt;&#39;a&gt; {
  15. fn new(env: JNIEnv&lt;&#39;a&gt;, ext_callback: JObject&lt;&#39;a&gt;) -&gt; Self {
  16. Callback {
  17. env: env,
  18. ext_callback: ext_callback,
  19. }
  20. }
  21. unsafe extern &quot;system&quot; fn invoke(
  22. &amp;mut self,
  23. totalfilesize: i64,
  24. totalbytestransferred: i64,
  25. streamsize: i64,
  26. streambytestransferred: i64,
  27. _dwstreamnumber: u32,
  28. _dwcallbackreason: LPPROGRESS_ROUTINE_CALLBACK_REASON,
  29. _hsourcefile: HANDLE,
  30. _hdestinationfile: HANDLE,
  31. _lpdata: *const ::core::ffi::c_void,
  32. ) -&gt; u32 {
  33. let arr = [
  34. JValueGen::Long(totalfilesize),
  35. JValueGen::Long(totalbytestransferred),
  36. JValueGen::Long(streamsize),
  37. JValueGen::Long(streambytestransferred),
  38. ];
  39. self.env
  40. .call_method(&amp;self.callback, &quot;onProgressEvent&quot;, &quot;(IIII)V&quot;, &amp;arr)
  41. .expect(&quot;Java callback failed&quot;);
  42. return 0;
  43. }
  44. }

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:

  1. pub extern &quot;system&quot; fn Java_com_nhbb_util_natives_WindowsCopy_copy&lt;&#39;local&gt;(
  2. mut env: JNIEnv&lt;&#39;local&gt;,
  3. _object: JObject&lt;&#39;local&gt;,
  4. source: JString&lt;&#39;local&gt;,
  5. dest: JString&lt;&#39;local&gt;,
  6. flags: jint,
  7. ext_callback: JObject&lt;&#39;local&gt;,
  8. ) {
  9. let source_jstr: JavaStr = env.get_string(&amp;source).expect(&quot;Invalid source string&quot;);
  10. let dest_jstr: JavaStr = env.get_string(&amp;dest).expect(&quot;Invalid dest string&quot;);
  11. let source_arr = source_jstr.get_raw();
  12. let dest_arr = dest_jstr.get_raw();
  13. let source = source_arr as *const u8;
  14. let dest = dest_arr as *const u8;
  15. let flags: u32 = flags.try_into().unwrap();
  16. let callback = Callback::new(env, ext_callback);
  17. unsafe {
  18. CopyFileExA(
  19. PCSTR(source),
  20. PCSTR(dest),
  21. LPPROGRESS_ROUTINE::Some(callback::invoke),
  22. // ^^^^^^^^ use of undeclared crate or module `callback`
  23. None,
  24. None,
  25. flags,
  26. );
  27. }
  28. }

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.

  1. use std::ffi::c_void;
  2. use encoding_rs::WINDOWS_1252;
  3. use windows::core::*;
  4. use windows::Win32::Foundation::HANDLE;
  5. use windows::Win32::Storage::FileSystem::{
  6. CopyFileExA, LPPROGRESS_ROUTINE, LPPROGRESS_ROUTINE_CALLBACK_REASON,
  7. };
  8. struct Callback&lt;&#39;a&gt; {
  9. ext_callback: &amp;&#39;a mut dyn FnMut(i64, i64, i64, i64) -&gt; u32,
  10. }
  11. impl&lt;&#39;a&gt; Callback&lt;&#39;a&gt; {
  12. fn new(ext_callback: &amp;&#39;a mut dyn FnMut(i64, i64, i64, i64) -&gt; u32) -&gt; Self {
  13. Callback { ext_callback }
  14. }
  15. unsafe extern &quot;system&quot; fn invoke(
  16. totalfilesize: i64,
  17. totalbytestransferred: i64,
  18. streamsize: i64,
  19. streambytestransferred: i64,
  20. _dwstreamnumber: u32,
  21. _dwcallbackreason: LPPROGRESS_ROUTINE_CALLBACK_REASON,
  22. _hsourcefile: HANDLE,
  23. _hdestinationfile: HANDLE,
  24. lpdata: *const c_void,
  25. ) -&gt; u32 {
  26. let this_ptr: *mut Self = lpdata.cast_mut().cast();
  27. let this = this_ptr.as_mut().unwrap();
  28. (this.ext_callback)(
  29. totalfilesize,
  30. totalbytestransferred,
  31. streamsize,
  32. streambytestransferred,
  33. )
  34. }
  35. }
  36. fn main() {
  37. let mut ext_callback =
  38. |totalfilesize, totalbytestransferred, streamsize, streambytestransferred| {
  39. println!(
  40. &quot;Progress: File: {}/{}, Stream Size: {}, Stream bytes transferred: {}&quot;,
  41. totalbytestransferred, totalfilesize, streamsize, streambytestransferred,
  42. );
  43. 0
  44. };
  45. let mut callback = Callback::new(&amp;mut ext_callback);
  46. // IMPORTANT: Rust strings are UTF-8, but `CopyFileExA` requires an ANSI/Windows-1252 string!
  47. // Use `encoding_rs` to convert between the two.
  48. let mut source = WINDOWS_1252.encode(&quot;file_a.txt&quot;).0.into_owned();
  49. let mut dest = WINDOWS_1252.encode(&quot;file_b.txt&quot;).0.into_owned();
  50. // Add null termination
  51. source.push(0);
  52. dest.push(0);
  53. unsafe {
  54. CopyFileExA(
  55. PCSTR::from_raw(source.as_ptr()),
  56. PCSTR::from_raw(dest.as_ptr()),
  57. LPPROGRESS_ROUTINE::Some(Callback::invoke),
  58. Some(((&amp;mut callback) as *mut Callback).cast()),
  59. None,
  60. 0,
  61. );
  62. }
  63. }
  1. Progress: File: 0/23, Stream Size: 23, Stream bytes transferred: 0
  2. 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:

  1. use std::ffi::c_void;
  2. use std::iter;
  3. use windows::core::*;
  4. use windows::Win32::Foundation::HANDLE;
  5. use windows::Win32::Storage::FileSystem::{
  6. CopyFileExW, LPPROGRESS_ROUTINE, LPPROGRESS_ROUTINE_CALLBACK_REASON,
  7. };
  8. struct Callback&lt;&#39;a&gt; {
  9. ext_callback: &amp;&#39;a mut dyn FnMut(i64, i64, i64, i64) -&gt; u32,
  10. }
  11. impl&lt;&#39;a&gt; Callback&lt;&#39;a&gt; {
  12. fn new(ext_callback: &amp;&#39;a mut dyn FnMut(i64, i64, i64, i64) -&gt; u32) -&gt; Self {
  13. Callback { ext_callback }
  14. }
  15. unsafe extern &quot;system&quot; fn invoke(
  16. totalfilesize: i64,
  17. totalbytestransferred: i64,
  18. streamsize: i64,
  19. streambytestransferred: i64,
  20. _dwstreamnumber: u32,
  21. _dwcallbackreason: LPPROGRESS_ROUTINE_CALLBACK_REASON,
  22. _hsourcefile: HANDLE,
  23. _hdestinationfile: HANDLE,
  24. lpdata: *const c_void,
  25. ) -&gt; u32 {
  26. let this_ptr: *mut Self = lpdata.cast_mut().cast();
  27. let this = this_ptr.as_mut().unwrap();
  28. (this.ext_callback)(
  29. totalfilesize,
  30. totalbytestransferred,
  31. streamsize,
  32. streambytestransferred,
  33. )
  34. }
  35. }
  36. fn main() {
  37. let mut ext_callback =
  38. |totalfilesize, totalbytestransferred, streamsize, streambytestransferred| {
  39. println!(
  40. &quot;Progress: File: {}/{}, Stream Size: {}, Stream bytes transferred: {}&quot;,
  41. totalbytestransferred, totalfilesize, streamsize, streambytestransferred,
  42. );
  43. 0
  44. };
  45. let mut callback = Callback::new(&amp;mut ext_callback);
  46. // IMPORTANT: Rust strings are UTF-8, but `CopyFileExW`
  47. // requires UTF-16 with null termination.
  48. let source: Vec&lt;u16&gt; = &quot;file_&#128512;_a.txt&quot;
  49. .encode_utf16()
  50. .chain(iter::once(0)) // null termination
  51. .collect();
  52. let dest: Vec&lt;u16&gt; = &quot;file_&#128512;_b.txt&quot;
  53. .encode_utf16()
  54. .chain(iter::once(0)) // null termination
  55. .collect();
  56. unsafe {
  57. CopyFileExW(
  58. PCWSTR::from_raw(source.as_ptr()),
  59. PCWSTR::from_raw(dest.as_ptr()),
  60. LPPROGRESS_ROUTINE::Some(Callback::invoke),
  61. Some(((&amp;mut callback) as *mut Callback).cast()),
  62. None,
  63. 0,
  64. );
  65. }
  66. }
  1. Progress: File: 0/30, Stream Size: 30, Stream bytes transferred: 0
  2. 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:

确定