Rust中实现CopyFileExA的LPPROGRESS_ROUTINE回调包装器遇阻求助
问题:Rust中调用Windows CopyFileExA时的LPPROGRESS_ROUTINE回调实现问题
我是一名正在学习Rust的Java程序员,希望在Rust程序中调用Windows原生的CopyFileExA函数,但在实现LPPROGRESS_ROUTINE回调时遇到了困难。在Java中我会用实现接口的类来做回调,但Rust里lpprogressroutine参数是函数指针而非多态对象。
我希望回调能调用带额外参数的函数,因此创建了一个包含JNIEnv和外部回调对象的Callback结构体:
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; } }
为访问结构体字段,我给invoke方法添加了&mut self参数,这可能破坏了LPPROGRESS_ROUTINE的函数签名。之后在调用CopyFileExA时,我无法获取非静态invoke方法的指针,出现“use of undeclared crate or module callback”错误。
请问使用struct的方式是否正确?有没有更好的实现方法?
解决方案
你的思路方向是对的,但实现细节没贴合Windows API回调的设计逻辑。Windows的CopyFileExA允许通过最后一个lpData参数传递自定义上下文,这正是传递JNI环境和Java回调对象的正确方式,不需要把方法绑定到结构体实例上。
步骤1:修正回调函数签名
LPPROGRESS_ROUTINE要求的是符合extern "system"调用约定的静态函数,不能带&mut self参数。我们可以把上下文数据通过lpData传递,在回调内部将其转换回结构体指针。
步骤2:调整结构体与回调实现
重新设计后的代码如下:
use jni::objects::{JObject, JValueGen}; use jni::JNIEnv; use windows::core::*; use windows::Win32::Foundation::HANDLE; use windows::Win32::Storage::FileSystem::{ CopyFileExA, LPPROGRESS_ROUTINE, LPPROGRESS_ROUTINE_CALLBACK_REASON, }; // 定义上下文结构体,存放JNI环境和Java回调对象 #[derive(Debug)] struct CallbackContext<'a> { env: JNIEnv<'a>, ext_callback: JObject<'a>, } // 静态回调函数,严格匹配LPPROGRESS_ROUTINE的签名 unsafe extern "system" fn progress_callback( total_filesize: i64, total_bytes_transferred: i64, stream_size: i64, stream_bytes_transferred: i64, _dw_stream_number: u32, _dw_callback_reason: LPPROGRESS_ROUTINE_CALLBACK_REASON, _h_source_file: HANDLE, _h_destination_file: HANDLE, lp_data: *const ::core::ffi::c_void, ) -> u32 { // 将lpData转换回上下文结构体指针 let context = &mut *(lp_data as *mut CallbackContext); // 调用Java回调方法,注意签名匹配:Java的long对应JNI的jlong,所以用"(JJJJ)V" let args = [ JValueGen::Long(total_filesize), JValueGen::Long(total_bytes_transferred), JValueGen::Long(stream_size), JValueGen::Long(stream_bytes_transferred), ]; if let Err(e) = context.env.call_method(&context.ext_callback, "onProgressEvent", "(JJJJ)V", &args) { eprintln!("调用Java回调失败: {:?}", e); // 返回非0值可终止CopyFileExA操作 return 1; } // 返回0表示继续复制 0 } // 使用示例 pub fn copy_with_callback<'a>( env: JNIEnv<'a>, src: &str, dest: &str, callback: JObject<'a>, ) -> Result<()> { let context = CallbackContext { env, ext_callback: callback }; // 转换为C风格ANSI字符串,适配CopyFileExA的参数要求 let src_c = std::ffi::CString::new(src)?; let dest_c = std::ffi::CString::new(dest)?; unsafe { CopyFileExA( src_c.as_ptr(), dest_c.as_ptr(), Some(progress_callback), // 传递上下文指针 &context as *const _ as *mut _, None, 0, ).ok()?; } Ok(()) }
关键说明
- 上下文传递:通过
CopyFileExA的lpData参数把CallbackContext指针传给回调,这是Windows API回调传递自定义数据的标准方式。 - 静态回调函数:必须用静态函数匹配
LPPROGRESS_ROUTINE的签名,Windows无法识别Rust的实例方法调用约定。 - JNI方法签名修正:之前的
(IIII)V错误,Java的long对应JNI的jlong(64位),正确签名是(JJJJ)V,否则会出现方法找不到或参数类型错误。 - 生命周期管理:确保
CallbackContext的生命周期覆盖CopyFileExA的整个执行过程,避免悬垂指针。
注意事项
- 不要在回调中执行耗时操作,否则会阻塞复制进程。
- 处理JNI调用错误时,返回非0值可终止复制,可根据需求调整返回值。
- 若需可变访问上下文,可将
CallbackContext声明为可变,转换指针时用*mut。
内容的提问来源于stack exchange,提问作者Cardinal System
相关产品推荐
相关产品推荐

