4.1 KiB
4.1 KiB
Windows CMD 窗口问题根本原因分析
问题现象
即使代码中有 CREATE_NO_WINDOW 标志,Windows 上启动所有服务时仍然弹出 CMD 窗口:
- mcp-proxy.cmd
- qiming-file-server.cmd
- qiming-lanproxy.exe
根本原因
❌ 当前实现的问题
// 在闭包中调用 .no_window()
let mut cmd = CommandWrap::with_new(bin_path, |cmd| {
cmd.no_window() // ← 这里设置 CREATE_NO_WINDOW
.arg("...")
.env("...", "...");
});
// 闭包外再设置 CreationFlags
#[cfg(windows)]
let child = cmd
.wrap(CreationFlags(CREATE_NO_WINDOW | DETACHED_PROCESS)) // ← 这里又设置一次
.wrap(JobObject)
.spawn()?;
问题分析
CommandWrap::with_new 的工作原理:
// process-wrap 库的实现(简化)
impl CommandWrap {
pub fn with_new<F>(program: &str, f: F) -> Self
where F: FnOnce(&mut Command)
{
let mut cmd = Command::new(program);
f(&mut cmd); // 闭包修改 Command
Self {
cmd, // ← Command 存储在这里
// ...
}
}
}
问题:
- 闭包里
cmd.no_window()设置了creation_flags(CREATE_NO_WINDOW) - 但这个设置在
cmd被包装进CommandWrap后无法继承 - 后续的
.wrap(CreationFlags(...))设置的是 process-wrap 自己的标志 - 两者不会合并! process-wrap 会用自己的
CreationFlags覆盖原始的
验证
查看 process-wrap 源码(推测):
// process-wrap 的 CreationFlags wrapper
impl CreationFlags {
pub fn apply(&self, cmd: &mut Command) {
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
cmd.creation_flags(self.0); // ← 直接覆盖,不合并!
}
}
}
所以实际发生的是:
- 闭包内:
cmd.creation_flags(CREATE_NO_WINDOW)✅ - 闭包外:
cmd.creation_flags(CREATE_NO_WINDOW | DETACHED_PROCESS)← 覆盖了! - 但如果 process-wrap 内部创建了新的 Command,第 1 步的设置会丢失!
解决方案
方案 A:只在 process-wrap 层面设置(推荐)
删除闭包内的 .no_window(),只依赖 .wrap(CreationFlags(...))
let mut cmd = CommandWrap::with_new(bin_path, |cmd| {
// ❌ 删除这行:cmd.no_window()
cmd.arg("...")
.env("...", "...");
});
#[cfg(windows)]
let child = cmd
.wrap(CreationFlags(CREATE_NO_WINDOW | DETACHED_PROCESS)) // ✅ 只在这里设置
.wrap(JobObject)
.spawn()?;
方案 B:确保标志合并
在闭包外先读取现有标志,然后合并
let mut cmd = CommandWrap::with_new(bin_path, |cmd| {
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
const CREATE_NO_WINDOW: u32 = 0x08000000;
const DETACHED_PROCESS: u32 = 0x00000008;
cmd.creation_flags(CREATE_NO_WINDOW | DETACHED_PROCESS);
}
cmd.arg("...")
.env("...", "...");
});
// Windows: 不需要再 wrap CreationFlags,因为已经在闭包里设置了
#[cfg(windows)]
let child = cmd
.wrap(JobObject) // ❌ 删除 CreationFlags wrap
.wrap(KillOnDrop)
.spawn()?;
方案 C:完全不用 process-wrap 的 CreationFlags
直接在 Command 上设置,不依赖 process-wrap
use std::process::Stdio;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
let mut cmd = tokio::process::Command::new(bin_path);
cmd.args(&["start", "--port", "8080"])
.env("PATH", &node_path)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
#[cfg(windows)]
{
const CREATE_NO_WINDOW: u32 = 0x08000000;
const DETACHED_PROCESS: u32 = 0x00000008;
cmd.creation_flags(CREATE_NO_WINDOW | DETACHED_PROCESS);
}
let mut child = cmd.spawn()?;
// 手动管理进程树(如果需要)
#[cfg(windows)]
{
// 可以使用 windows-rs 创建 Job Object
}
推荐修复
方案 A 最简单,只需删除闭包内的 .no_window() 调用。
但我怀疑问题可能更深层:process-wrap 的 CreationFlags 可能根本没生效!
让我检查 process-wrap 的版本和实现...