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

求助:Rust Tauri应用中运行外部EXE时隐藏Windows终端

问题解决:Tauri发布模式下隐藏外部exe终端窗口+子进程终止优化

一、隐藏终端窗口的核心方案

你遇到的问题根源是:windows_subsystem = "windows"仅控制Tauri主程序的窗口状态,对通过Command启动的外部子进程无效。外部exe作为控制台程序,发布模式下启动时会默认创建终端窗口,必须通过Windows进程创建标志禁用。

方案1:直接调用Windows API(无额外依赖)

修改src-tauri/my_class.rs的execute方法,通过CreateProcessW指定CREATE_NO_WINDOW标志:

use std::env;
use std::sync::{RwLock, Arc};
use std::process::Child;
use windows_sys::Win32::System::Threading::{CreateProcessW, STARTUPINFOW, PROCESS_INFORMATION, CREATE_NO_WINDOW};
use windows_sys::Win32::Foundation::{BOOL, DWORD, LPWSTR};
use std::os::windows::ffi::OsStrExt;
use std::ptr;

pub struct MyClass {
    some_var: String,
    child: Arc<RwLock<Option<Child>>>, // 改用Arc支持跨线程访问
}

impl MyClass {
    // 保留原有构造方法...

    pub fn execute(&self) -> Result<(), Box<dyn std::error::Error>> {
        let exe_path = env::current_dir()?.join("my_app.exe");
        let args_str = format!(
            "some args {}",
            env::current_dir()?.join("data").join("more data.json").display()
        );

        // 转换为Windows要求的宽字符串
        let exe_wide: Vec<u16> = exe_path.as_os_str().encode_wide().chain(std::iter::once(0)).collect();
        let args_wide: Vec<u16> = args_str.encode_wide().chain(std::iter::once(0)).collect();

        let mut startup_info: STARTUPINFOW = unsafe { std::mem::zeroed() };
        startup_info.cb = std::mem::size_of::<STARTUPINFOW>() as DWORD;
        let mut proc_info: PROCESS_INFORMATION = unsafe { std::mem::zeroed() };

        // 创建进程时禁用窗口
        let success = unsafe {
            CreateProcessW(
                exe_wide.as_ptr(),
                args_wide.as_mut_ptr(),
                ptr::null_mut(),
                ptr::null_mut(),
                BOOL(0), // 不继承父进程句柄
                CREATE_NO_WINDOW,
                ptr::null_mut(),
                ptr::null(),
                &mut startup_info,
                &mut proc_info,
            )
        };

        if success == 0 {
            return Err("启动外部进程失败".into());
        }

        // 关闭无用句柄
        unsafe {
            windows_sys::Win32::Foundation::CloseHandle(proc_info.hThread);
            windows_sys::Win32::Foundation::CloseHandle(proc_info.hProcess);
        }

        // 转换为标准库Child实例以便后续管理
        let child = std::process::Child::from_raw_handle(proc_info.hProcess as _)?;
        let mut lock = self.child.write()?;
        *lock = Some(child);

        Ok(())
    }
}

方案2:用第三方库简化代码

添加command-ext依赖到Cargo.toml:

[dependencies]
command-ext = "0.2"

修改execute方法,仅需一行代码启用无窗口:

use command_ext::CommandExt;

pub fn execute(&self) -> Result<(), Box<dyn std::error::Error>> {
    let child = Command::new(env::current_dir()?.join("my_app.exe"))
        .arg("some args")
        .arg(env::current_dir()?.join("data").join("more data.json"))
        .create_no_window(true) // 关键配置:禁用终端窗口
        .spawn()?;

    let mut lock = self.child.write()?;
    *lock = Some(child);
    Ok(())
}

二、子进程终止的优化实现

你当前用RwLock<Option<Child>>的思路可行,可通过以下方式优化:

1. 封装终止方法

在MyClass中添加专门的终止函数,处理优雅终止+强制杀死逻辑:

impl MyClass {
    // 保留原有方法...

    pub fn terminate_child(&self) -> Result<(), Box<dyn std::error::Error>> {
        let mut lock = self.child.write()?;
        if let Some(mut child) = lock.take() {
            // 先尝试检查进程是否已结束,未结束则强制杀死
            if child.try_wait()?.is_none() {
                child.kill()?;
            }
        }
        Ok(())
    }
}

2. 绑定应用生命周期

在main.rs中添加窗口关闭钩子,自动终止子进程:

use std::sync::Arc;

fn main() {
    let my_class = Arc::new(MyClass::new("your_var"));
    let class_clone = my_class.clone();

    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![my_actual_handler, terminate_child])
        .on_window_event(move |event| {
            if let tauri::WindowEvent::CloseRequested { .. } = event.event() {
                // 窗口关闭时自动终止子进程
                let _ = class_clone.terminate_child();
            }
        })
        .run(tauri::generate_context!())
        .expect("应用启动失败");
}

// 暴露给前端的终止命令
#[tauri::command]
fn terminate_child(my_class: tauri::State<'_, Arc<MyClass>>) -> Result<(), String> {
    my_class.terminate_child().map_err(|e| e.to_string())
}

关键说明

  • CREATE_NO_WINDOW是Windows平台独有的进程创建标志,专门用于禁用控制台窗口,和主程序的windows_subsystem设置完全独立。
  • 用Arc<RwLock<Option<Child>>>是为了在多线程场景(比如UI触发终止命令)下安全共享子进程实例,避免数据竞争。

内容的提问来源于stack exchange,提问作者Alexander Prokopenko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 08:40:39