我想从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;
}
}
现在,为了访问我在结构体上定义的env
和ext_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
的方法正确吗?有更好的方法吗?
1条答案
按热度按时间dxxyhpgq1#
这里需要理解的重要一点是,
callback::invoke
不是一个已经绑定到Callback
对象的普通函数。Callback::invoke
需要Callback
选项作为它的第一个参数。这意味着它不会也永远不会直接与LPPROGRESS_ROUTINE
一起工作。LPPROGRESS_ROUTINE
需要静态函数。所以你的解决方案是创建一个静态函数,通过它的
lpdata
参数传递callback
选项。这正是lpdata
参数的作用。请注意,因为
lpdata
参数传递给C并返回,所以它将是一个原始指针,您将需要unsafe
来使用它。下面是一个如何工作的演示。注意,我去掉了所有JNI的东西,但同样的原理也适用于JNI。
其他备注,重要说明:
PCSTR
的编码取决于语言环境,因此不应该像前面所示的那样使用**。虽然大多数西方语言环境确实使用Windows-1252
,但许多其他语言环境并不使用。因此,建议使用
PCWSTR
,这是全局明确的。根据Windows API文档,
PCWSTR
被编码为UTF-16LE
。这是前面的代码将如何重写它: