From 0741a56c99e1386332f1a28dbe87811ed4a7decc Mon Sep 17 00:00:00 2001 From: zqm Date: Mon, 13 Apr 2026 10:00:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Windows/CS/Framework4.0/Updater/Cargo.toml | 1 + Windows/CS/Framework4.0/Updater/src/main.rs | 154 ++++++++++++++++---- 2 files changed, 129 insertions(+), 26 deletions(-) diff --git a/Windows/CS/Framework4.0/Updater/Cargo.toml b/Windows/CS/Framework4.0/Updater/Cargo.toml index a3de75b..45cbcf7 100644 --- a/Windows/CS/Framework4.0/Updater/Cargo.toml +++ b/Windows/CS/Framework4.0/Updater/Cargo.toml @@ -12,6 +12,7 @@ windows = { version = "0.56", features = [ "Win32_System_Console", "Win32_System_Pipes", "Win32_System_IO", + "Win32_System_Threading", "Win32_Foundation", "Win32_Security", "Win32_Storage", diff --git a/Windows/CS/Framework4.0/Updater/src/main.rs b/Windows/CS/Framework4.0/Updater/src/main.rs index e43d62f..1cf5647 100644 --- a/Windows/CS/Framework4.0/Updater/src/main.rs +++ b/Windows/CS/Framework4.0/Updater/src/main.rs @@ -1419,20 +1419,48 @@ fn notify_app_upgrade(app_name: &str, current_ver: &str, latest_ver: &str, _debu app_name, current_ver, latest_ver ); - // 3. 对每个 PID 直接用 Win32 命名管道发送(不再用 PowerShell) + // 3. 对每个 PID 发送升级确认并等待用户响应(双向握手) for pid in pids { let pipe_name = format!("\\\\.\\pipe\\EasyTest_{}", pid); let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); - log_print!("{} [升级确认] 连接到 {} (PID={})", ts, pipe_name, pid); + log_print!("{} [升级确认] 连接到 {} (PID={}),等待响应...", ts, pipe_name, pid); - // 直接用 windows-rs Win32 API 连接命名管道 - let pipe_result = connect_named_pipe_byte(&pipe_name, &msg); + // 发送 UpgradeConfirm 并等待 UpgradeResponse(超时 10 秒) + let response = connect_and_wait_response(&pipe_name, &msg, 10000); - if pipe_result { - log_print!("{} [升级确认] 已通知 {} (PID={}): {} -> {}", - ts, app_name, pid, current_ver, latest_ver); - } else { - log_print!("{} [升级确认] 连接 {} (PID={}) 失败", ts, app_name, pid); + match response { + Ok(true) => { + log_print!("{} [升级确认] 用户批准升级 {} (PID={}),应用将退出", + ts, app_name, pid); + // 等待应用退出(最多 5 秒) + log_print!("{} [升级确认] 等待 {} 退出...", ts, app_name); + let start = std::time::Instant::now(); + loop { + let check = Command::new("powershell") + .args(["-NoProfile", "-NonInteractive", "-Command", + &format!("if ((Get-Process -Id {pid} -ErrorAction SilentlyContinue) -eq $null) {{ 'exit' }} else {{ 'running' }}")]) + .output(); + if let Ok(out) = check { + if String::from_utf8_lossy(&out.stdout).contains("exit") { + log_print!("{} [升级确认] {} 已退出,升级完成", ts, app_name); + break; + } + } + if start.elapsed().as_secs() > 5 { + log_print!("{} [升级确认] 等待退出超时,跳过", ts); + break; + } + std::thread::sleep(std::time::Duration::from_millis(500)); + } + } + Ok(false) => { + log_print!("{} [升级确认] 用户拒绝升级 {} (PID={})", + ts, app_name, pid); + } + Err(e) => { + log_print!("{} [升级确认] 通信失败 {} (PID={}): {}", + ts, app_name, pid, e); + } } } } @@ -1444,14 +1472,21 @@ fn notify_app_upgrade(app_name: &str, current_ver: &str, latest_ver: &str, _debu } } -/// 用 Win32 API 连接命名管道并发送字节数据(字节模式) -fn connect_named_pipe_byte(pipe_name: &str, msg: &str) -> bool { - use windows::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE}; - use windows::Win32::Storage::FileSystem::{CreateFileW, WriteFile, FILE_ATTRIBUTE_NORMAL}; +/// 用 Win32 API 连接命名管道,发送消息后等待响应(双向握手) +/// 返回 Ok(true) = 用户批准,Ok(false) = 用户拒绝,Err = 通信失败/超时 +fn connect_and_wait_response( + pipe_name: &str, + msg: &str, + timeout_ms: u32, +) -> Result { + use windows::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE, HANDLE}; + use windows::Win32::Storage::FileSystem::{CreateFileW, WriteFile, ReadFile, FILE_ATTRIBUTE_NORMAL}; + use windows::Win32::System::Threading::CreateEventW; use windows::core::PCWSTR; - // Windows API 常量(跨版本稳定) + // Windows API 常量 use windows::Win32::Storage::FileSystem::{FILE_SHARE_MODE, FILE_CREATION_DISPOSITION}; + const GENERIC_READ: u32 = 0x80000000u32; const GENERIC_WRITE: u32 = 0x40000000u32; const FILE_SHARE_NONE: u32 = 0u32; const OPEN_EXISTING: u32 = 3u32; @@ -1459,11 +1494,11 @@ fn connect_named_pipe_byte(pipe_name: &str, msg: &str) -> bool { // 将管道名转换为宽字符串 let wide_name: Vec = pipe_name.encode_utf16().chain(std::iter::once(0)).collect(); - // 用 CreateFileW 打开命名管道(写模式,字节模式) + // 打开命名管道(同时读写模式) let pipe = unsafe { CreateFileW( PCWSTR::from_raw(wide_name.as_ptr()), - GENERIC_WRITE, + GENERIC_READ | GENERIC_WRITE, FILE_SHARE_MODE(FILE_SHARE_NONE), None, FILE_CREATION_DISPOSITION(OPEN_EXISTING), @@ -1472,18 +1507,13 @@ fn connect_named_pipe_byte(pipe_name: &str, msg: &str) -> bool { ) }; - // CreateFileW 返回 Result,需要 unwrap let pipe = match pipe { Ok(h) => h, - Err(e) => { - eprintln!("[Pipe] CreateFileW failed: {:?}", e); - return false; - } + Err(e) => return Err(format!("连接管道失败: {:?}", e)), }; - // 检查是否成功打开 if pipe == INVALID_HANDLE_VALUE { - return false; + return Err("管道句柄无效".to_string()); } // 写入消息 + 换行(C# 端用 StreamReader.ReadLine 读取) @@ -1492,7 +1522,7 @@ fn connect_named_pipe_byte(pipe_name: &str, msg: &str) -> bool { let data = msg_with_newline.as_bytes(); let mut written: u32 = 0; - let success = unsafe { + let write_ok = unsafe { WriteFile( pipe, Some(data), @@ -1501,9 +1531,81 @@ fn connect_named_pipe_byte(pipe_name: &str, msg: &str) -> bool { ) }; - let _ = unsafe { CloseHandle(pipe) }; + if write_ok.is_err() || written == 0 { + unsafe { let _ = CloseHandle(pipe); }; + return Err("写入消息失败".to_string()); + } - success.is_ok() && written > 0 + // 创建事件用于等待响应(失败则用 INVALID_HANDLE_VALUE) + let event = unsafe { CreateEventW(None, true, false, None) } + .unwrap_or(HANDLE(0)); + + let mut buf = [0u8; 4096]; + let mut bytes_read: u32 = 0; + let mut resp_buf = Vec::new(); + + // 循环读取直到收到完整响应(读到 \n)或超时 + let start_time = std::time::Instant::now(); + let deadline = timeout_ms as u64; + + loop { + // 检查是否超时 + if start_time.elapsed().as_millis() as u64 > deadline { + if event != INVALID_HANDLE_VALUE { + unsafe { let _ = CloseHandle(event); }; + } + unsafe { let _ = CloseHandle(pipe); }; + return Err("等待响应超时".to_string()); + } + + // 尝试读取数据(非阻塞轮询) + let result = unsafe { + ReadFile( + pipe, + Some(&mut buf), + Some(&mut bytes_read), + None, + ) + }; + + if result.is_ok() && bytes_read > 0 { + resp_buf.extend_from_slice(&buf[..bytes_read as usize]); + + // 检查是否包含换行符(完整消息) + if resp_buf.contains(&b'\n') { + break; + } + } + + // 短暂等待后重试 + std::thread::sleep(std::time::Duration::from_millis(50)); + } + + if event != INVALID_HANDLE_VALUE { + unsafe { let _ = CloseHandle(event); }; + } + unsafe { let _ = CloseHandle(pipe); }; + + // 解析响应 JSON + let response = String::from_utf8_lossy(&resp_buf) + .trim_end_matches(|c| c == '\r' || c == '\n') + .to_string(); + + // 调试:打印原始响应 + { + let ts = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.3f"); + log_print!("{} [升级确认] 收到原始响应: {:?}", ts, response); + } + + // 解析 {"Type":"UpgradeResponse","Approved":true/false} + if let Some(start) = response.find("\"Approved\":") { + // "Approved": 共11个字符,true/false 从 start+11 开始 + let rest = &response[start + 11..]; + let approved = rest.starts_with("true"); + return Ok(approved); + } + + Err(format!("无法解析响应: {}", response)) } /// 通知所有升级了的应用(EasyTest 等)