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

Rust实现阻止Windows关机:无法接收WM_QUERYENDSESSION消息求助

解决WM_QUERYENDSESSION消息无法在独立线程接收的问题

问题根源

Windows系统仅会将WM_QUERYENDSESSION及相关关机广播消息发送给主线程(UI线程)创建的顶层可见窗口,独立线程创建的窗口不会收到这类系统级消息——系统默认只向主线程的消息队列广播关机通知。你看到的799(WM_DWMCOMPOSITIONCHANGED)、800(WM_DWMNCRENDERINGCHANGED)消息属于桌面窗口管理器的内部通知,和关机流程无关。

解决方案步骤

  1. 将窗口创建与消息循环移至主线程:确保监听窗口是主线程的顶层窗口,系统才会向其发送关机消息。
  2. 关键任务独立执行:把需要阻塞关机的关键逻辑放在独立线程,主线程仅负责消息监听与关机拦截。
  3. 线程匹配调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 12:57:10