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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 01:00:17