You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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(())
}

关键说明

  1. 上下文传递:通过CopyFileExA的lpData参数把CallbackContext指针传给回调,这是Windows API回调传递自定义数据的标准方式。
  2. 静态回调函数:必须用静态函数匹配LPPROGRESS_ROUTINE的签名,Windows无法识别Rust的实例方法调用约定。
  3. JNI方法签名修正:之前的(IIII)V错误,Java的long对应JNI的jlong(64位),正确签名是(JJJJ)V,否则会出现方法找不到或参数类型错误。
  4. 生命周期管理:确保CallbackContext的生命周期覆盖CopyFileExA的整个执行过程,避免悬垂指针。

注意事项

  • 不要在回调中执行耗时操作,否则会阻塞复制进程。
  • 处理JNI调用错误时,返回非0值可终止复制,可根据需求调整返回值。
  • 若需可变访问上下文,可将CallbackContext声明为可变,转换指针时用*mut。

内容的提问来源于stack exchange,提问作者Cardinal System

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 14:19:55