英文:
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.
现在,为了访问我在结构体上定义的env
和ext_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<'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.
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 "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?
答案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<'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
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<'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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论