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

Rust Win32窗口PeekMessage无法捕获WM_QUIT导致主循环无法退出

Rust Win32窗口主循环无法捕获WM_QUIT消息问题

问题现象

使用Rust调用Win32 API编写窗口程序时,点击关闭按钮后窗口销毁,但主循环始终无法检测到WM_QUIT消息,程序持续运行无法退出。预期行为是调用PostQuitMessage(0)后,主循环捕获WM_QUIT并终止。

错误原因

  1. PeekMessageW参数过滤了WM_QUIT:PeekMessageW(&mut msg, self.hwnd, 0, 0, PM_REMOVE)中第二个参数指定了当前窗口的句柄self.hwnd,而WM_QUIT是线程级消息,不属于任何窗口,因此会被PeekMessage过滤,导致主循环永远接收不到该消息。
  2. 窗口过程中重复投递退出消息:WM_CLOSE分支中手动调用DestroyWindow(hwnd)并投递PostQuitMessage(0),而默认的DefWindowProcW处理WM_CLOSE时会自动调用DestroyWindow;同时WM_DESTROY分支又再次投递PostQuitMessage(0),虽不会直接导致循环无法退出,但属于冗余操作。

修复方案

1. 修改主循环的PeekMessage调用

将PeekMessageW的窗口句柄参数改为null_mut(),这样会接收当前线程的所有消息,包括WM_QUIT:

if unsafe { PeekMessageW(&mut msg, null_mut(), 0, 0, PM_REMOVE) } != 0 {

2. 修正窗口过程函数(window_proc)

移除WM_CLOSE分支中的手动操作,让系统默认处理WM_CLOSE(自动调用DestroyWindow),仅在WM_DESTROY中调用PostQuitMessage(0):

pub extern "system" fn window_proc(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
    match msg {
        WM_DESTROY => {
            unsafe { PostQuitMessage(0) };
            0
        }
        _ => return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
    }
}

完整修正后的win64.rs代码

#![cfg(windows)]

use std::error::Error;
use std::ptr::null_mut;
use std::mem::size_of;
use winapi::shared::minwindef::*;
use winapi::shared::windef::*;
use winapi::um::libloaderapi::{ GetModuleHandleW };
use winapi::um::wingdi::*;
use winapi::um::winuser::*;

fn to_wstring(s: &str) -> Vec<u16> {
    use std::os::windows::ffi::OsStrExt;
    std::ffi::OsStr::new(s).encode_wide().chain(std::iter::once(0)).collect()
}

pub extern "system" fn window_proc(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
    match msg {
        WM_DESTROY => {
            unsafe { PostQuitMessage(0) };
            0
        }
        _ => return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
    }
}

pub struct Window {
    h_instance: HINSTANCE,
    hwnd: HWND,
    rect: RECT,
}

impl Window {    
    pub fn new(title: &str, width: u32, height: u32) -> Result<Window, Box<(dyn Error + 'static)>>  {
        let title = to_wstring(title);
        let class_name = to_wstring("_class64");

        let mut wnd = Window {
            h_instance: unsafe { GetModuleHandleW(null_mut()) },
            hwnd: null_mut(),
            rect: RECT {
                left: 0,
                top: 0,
                right: width as i32,
                bottom: height as i32,
            }
        };

        if wnd.h_instance == null_mut() {
            let err_str = "failed to obtain window instance";
            unsafe { MessageBoxW(null_mut(), to_wstring(err_str).as_ptr(), to_wstring("Error").as_ptr(), MB_ICONERROR | MB_OK); }
            return Err(err_str.into());
        }

        let wnd_class_ex = WNDCLASSEXW {
            cbClsExtra: 0,
            cbSize: size_of::<WNDCLASSEXW>() as u32,
            cbWndExtra: 0,
            hbrBackground: unsafe { GetStockObject(BLACK_BRUSH as i32) as HBRUSH}, 
            hCursor: unsafe { LoadCursorW(null_mut(), IDC_ARROW) },
            hIcon: unsafe { LoadIconW(null_mut(), IDI_APPLICATION) },
            hIconSm: unsafe { LoadIconW(null_mut(), IDI_APPLICATION) },
            hInstance: wnd.h_instance,
            lpfnWndProc: Some(window_proc),
            lpszClassName: class_name.as_ptr(),
            lpszMenuName: null_mut(),
            style: CS_OWNDC | CS_HREDRAW | CS_VREDRAW,
        };

        if unsafe { RegisterClassExW(&wnd_class_ex) } == 0 {
            let err_str = "failed to register window";
            unsafe { MessageBoxW(null_mut(), to_wstring(err_str).as_ptr(), to_wstring("Error").as_ptr(), MB_ICONERROR | MB_OK); }
            return Err(err_str.into()); 
        }

        wnd.hwnd = unsafe { CreateWindowExW(
            0,
            class_name.as_ptr(),
            title.as_ptr(),
            WS_OVERLAPPEDWINDOW | WS_VISIBLE, 
            CW_USEDEFAULT, CW_USEDEFAULT,
            wnd.rect.right, wnd.rect.bottom, 
            null_mut(), null_mut(), wnd.h_instance, null_mut())
        };

        if wnd.hwnd == null_mut() {
            let err_str = "failed to create window";
            unsafe { MessageBoxW(null_mut(), to_wstring(err_str).as_ptr(), to_wstring("Error").as_ptr(), MB_ICONERROR | MB_OK); }
            return Err(err_str.into());
        }

        unsafe { ShowWindow(wnd.hwnd, SW_SHOW); }
        unsafe { UpdateWindow(wnd.hwnd); }

        Ok(wnd)
    }

    pub fn run(&mut self) -> isize {
        let mut msg = MSG {
            hwnd: null_mut(),
            lParam: 0,
            message: 0,
            pt: POINT {
                x: 0,
                y: 0
            },
            time: 0,
            wParam: 0,
        };

        loop {
            if unsafe { PeekMessageW(&mut msg, null_mut(), 0, 0, PM_REMOVE) } != 0 {
                if msg.message == WM_QUIT {
                    break;
                }

                unsafe { TranslateMessage(&msg); }
                unsafe { DispatchMessageW(&msg); }
            } else {
                // TODO: Add stuff here
            }
        }
        0
    }
}

内容的提问来源于stack exchange,提问作者fluffy-soft-dev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 03:05:27