如何在WPF中不创建Window实例订阅WM_QUERYENDSESSION消息
在无Window实例的WPF应用中接收WM_QUERYENDSESSION消息
问题背景
我有一个WPF应用,全程可能不会显示窗口,甚至不会实例化Window,希望接收WM_QUERYENDSESSION消息并进行相应处理。
我尝试了两种方案:
- 方案1:创建隐藏Window的方案可行,但必须依赖Window实例:
public partial class App : Application { private const int WM_QUERYENDSESSION = 0x0011; private HwndSource _hwndSource; protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); Log.Logger = new LoggerConfiguration().WriteTo.File("D:\\Something\\Test.log").CreateLogger(); Log.Information("Starting"); CreateHiddenWindow(); } private void CreateHiddenWindow() { Window hiddenWindow = new Window { Width = 0, Height = 0, ShowInTaskbar = false, WindowStyle = WindowStyle.None, AllowsTransparency = true, Background = System.Windows.Media.Brushes.Transparent }; hiddenWindow.SourceInitialized += (s, e) => { _hwndSource = (HwndSource)PresentationSource.FromVisual(hiddenWindow); _hwndSource.AddHook(WndProc); hiddenWindow.Hide(); }; hiddenWindow.Show(); } private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { Log.Information($"WndProc received message: {msg}"); if (msg == WM_QUERYENDSESSION) { Log.Information("WM_QUERYENDSESSION received in WndProc"); Log.Information("Handled WM_QUERYENDSESSION, flushing logs..."); Log.CloseAndFlush(); handled = true; } return IntPtr.Zero; } }
- 方案2:使用
ComponentDispatcher.ThreadFilterMessage的方案无效:能收到其他消息,但无法捕获WM_QUERYENDSESSION:
public partial class App : Application { private const int WM_QUERYENDSESSION = 0x0011; protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); Log.Logger = new LoggerConfiguration().WriteTo.File("D:\\Something\\Test.log").CreateLogger(); Log.Information("Starting"); ComponentDispatcher.ThreadFilterMessage += OnThreadFilterMessage; } private void OnThreadFilterMessage(ref MSG msg, ref bool handled) { Log.Information(msg.message.ToString()); if (msg.message == WM_QUERYENDSESSION) { Log.Information("WM_QUERYENDSESSION received in OnThreadFilterMessage"); Log.Information("Handled WM_QUERYENDSESSION, flushing logs..."); Log.CloseAndFlush(); handled = true; } } }
解决方案
核心原因:WM_QUERYENDSESSION是系统发送给顶层窗口的消息,ComponentDispatcher仅能捕获线程内的普通消息循环,没有顶层窗口句柄的话,系统不会将该消息投递到你的应用线程。
正确的做法是直接创建HwndSource(无需依赖Window实例),它会生成一个独立的顶层窗口句柄,从而能接收系统发送的会话结束消息:
public partial class App : Application { private const int WM_QUERYENDSESSION = 0x0011; private HwndSource _hwndSource; protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); Log.Logger = new LoggerConfiguration().WriteTo.File("D:\\Something\\Test.log").CreateLogger(); Log.Information("Starting"); // 直接创建独立的HwndSource,无需Window实例 CreateStandaloneHwndSource(); } private void CreateStandaloneHwndSource() { var hwndSourceParams = new HwndSourceParameters("HiddenMessageHandler") { Width = 0, Height = 0, WindowStyle = 0x80000000, // WS_POPUP 样式,无边框 ExtendedWindowStyle = 0x80, // WS_EX_TOOLWINDOW,不在任务栏显示 PositionX = -10000, // 将窗口移至屏幕外,彻底避免显示 PositionY = -10000 }; _hwndSource = new HwndSource(hwndSourceParams); _hwndSource.AddHook(WndProc); } private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (msg == WM_QUERYENDSESSION) { Log.Information("WM_QUERYENDSESSION received"); Log.Information("Flushing logs..."); Log.CloseAndFlush(); handled = true; // 返回IntPtr.Zero表示允许系统关机/注销;若需阻止可返回new IntPtr(1),但不建议随意阻止系统操作 return IntPtr.Zero; } return IntPtr.Zero; } protected override void OnExit(ExitEventArgs e) { // 退出时释放HwndSource资源 _hwndSource?.Dispose(); base.OnExit(e); } }
关键说明
HwndSource直接创建顶层窗口句柄,系统会向该句柄发送WM_QUERYENDSESSION消息- 通过窗口样式设置,确保窗口不会出现在任务栏或用户视野中
- 应用退出时记得释放
HwndSource,避免资源泄漏
内容的提问来源于stack exchange,提问作者cogumel0
相关产品推荐
相关产品推荐

