Rust实现阻止Windows关机:无法接收WM_QUERYENDSESSION消息求助
解决WM_QUERYENDSESSION消息无法在独立线程接收的问题
问题根源
Windows系统仅会将WM_QUERYENDSESSION及相关关机广播消息发送给主线程(UI线程)创建的顶层可见窗口,独立线程创建的窗口不会收到这类系统级消息——系统默认只向主线程的消息队列广播关机通知。你看到的799(WM_DWMCOMPOSITIONCHANGED)、800(WM_DWMNCRENDERINGCHANGED)消息属于桌面窗口管理器的内部通知,和关机流程无关。
解决方案步骤
- 将窗口创建与消息循环移至主线程:确保监听窗口是主线程的顶层窗口,系统才会向其发送关机消息。
- 关键任务独立执行:把需要阻塞关机的关键逻辑放在独立线程,主线程仅负责消息监听与关机拦截。
- 线程匹配调用API:
ShutdownBlockReasonCreate必须由窗口的创建线程(主线程)调用,否则API会执行失败。
Rust实现示例
use winapi::{ shared::minwindef::{BOOL, HWND, LPARAM, LRESULT, UINT, WPARAM}, um::{ winuser::{ CreateWindowExW, DefWindowProcW, DispatchMessageW, GetMessageW, MessageBoxW, PostQuitMessage, RegisterClassW, ShowWindow, UpdateWindow, WM_QUERYENDSESSION, WM_DESTROY, SW_SHOW, CS_HREDRAW, CS_VREDRAW, WS_OVERLAPPEDWINDOW, }, shutdown::{ShutdownBlockReasonCreate, ShutdownBlockReasonDestroy}, libloaderapi::GetModuleHandleW, }, }; use std::ptr; use std::thread; // 宽字符窗口类名 const CLASS_NAME: &[u16] = wide_str!("ShutdownBlockWindow"); // 窗口过程处理函数 unsafe extern "system" fn window_proc(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT { match msg { WM_QUERYENDSESSION => { // 显示窗口,满足Win32 API对可见窗口的要求 ShowWindow(hwnd, SW_SHOW); UpdateWindow(hwnd); // 设置关机阻止原因 let reason = wide_str!("正在执行关键任务,请完成后再关机"); ShutdownBlockReasonCreate(hwnd, reason.as_ptr()); // 弹出用户确认对话框 MessageBoxW(hwnd, wide_str!("是否允许关机?"), wide_str!("关键任务执行中"), 0); // 返回0阻止关机,后续任务完成后需解除阻止 0 } WM_DESTROY => { // 解除关机阻止 ShutdownBlockReasonDestroy(hwnd); PostQuitMessage(0); 0 } _ => DefWindowProcW(hwnd, msg, wparam, lparam), } } // 辅助函数:将字符串转为UTF-16宽字符 fn wide_str(s: &str) -> Vec<u16> { s.encode_utf16().chain(std::iter::once(0)).collect() } fn main() { unsafe { // 注册窗口类(主线程执行) let hinstance = GetModuleHandleW(ptr::null()); let wc = winapi::um::winuser::WNDCLASSEXW { cbSize: std::mem::size_of::<winapi::um::winuser::WNDCLASSEXW>() as UINT, style: CS_HREDRAW | CS_VREDRAW, lpfnWndProc: Some(window_proc), cbClsExtra: 0, cbWndExtra: 0, hInstance: hinstance, hIcon: ptr::null(), hCursor: ptr::null(), hbrBackground: ptr::null(), lpszMenuName: ptr::null(), lpszClassName: CLASS_NAME.as_ptr(), hIconSm: ptr::null(), }; RegisterClassW(&wc); // 创建顶层窗口(主线程执行) let hwnd = CreateWindowExW( 0, CLASS_NAME.as_ptr(), wide_str!("关机监听窗口").as_ptr(), WS_OVERLAPPEDWINDOW, winapi::um::winuser::CW_USEDEFAULT, winapi::um::winuser::CW_USEDEFAULT, 300, 200, ptr::null(), ptr::null(), hinstance, ptr::null(), ); // 启动关键任务线程 let hwnd_clone = hwnd; thread::spawn(move || { // 模拟关键任务执行(替换为实际业务逻辑) std::thread::sleep(std::time::Duration::from_secs(30)); // 任务完成后解除关机阻止并通知主线程退出 unsafe { ShutdownBlockReasonDestroy(hwnd_clone); winapi::um::winuser::PostMessageW(hwnd_clone, WM_DESTROY, 0, 0); } }); // 主线程消息循环,处理系统消息 let mut msg = std::mem::zeroed(); while GetMessageW(&mut msg, ptr::null(), 0, 0) > 0 { DispatchMessageW(&msg); } } }
注意事项
- 窗口需至少被显示过一次(哪怕最小化),才会被系统纳入关机消息的广播范围。
- 可通过
GetLastError排查ShutdownBlockReasonCreate的调用失败原因,确保调用线程与窗口创建线程一致。 - 关键任务完成后必须调用
ShutdownBlockReasonDestroy解除阻止,避免系统强制关机或程序残留。
内容的提问来源于stack exchange,提问作者MrSchildkr
相关产品推荐
相关产品推荐

