WPF应用程序捕获由任务管理器发起的强制结束

在聊天划水群刚刚好看到有个水友提出来。首先 我们来整理思路

任务管理器 →“结束任务”通常先给你的窗口发 WM_CLOSE(等价于用户点右上角 ×),如果你及时响应就能走到 WPF 的正常关闭流程(Window.Closing/Application.Exit/AppDomain.ProcessExit 等事件会触发)。
如果用户或系统直接强制终止(TerminateProcess),任何托管/托底事件都不会触发——你无法在进程被硬切掉时执行清理逻辑。
不能 100% 区分“任务管理器点了结束任务”与“用户点 ×”,因为二者都会是 WM_CLOSE 开始。若需区分来源,必须借助外部程序(看门狗)进程(在外部观察你被谁/何时杀掉)。

下面给出能捕获到的所有关闭路径及代码,外加一个Win32 消息钩子

using System;
using System.Windows;
using Microsoft.Win32; // SystemEvents
 
public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
 
        // 关机/注销(有 UI 机会)
        SystemEvents.SessionEnding += (_, args) =>
        {
            // args.Reason == SessionEndReasons.Logoff/Shutdown
            Log("SessionEnding: " + args.Reason);
            // 这里做尽量快的持久化和清理
        };
 
        // Dispatcher 未处理异常
        this.DispatcherUnhandledException += (_, exArgs) =>
        {
            Log("DispatcherUnhandledException: " + exArgs.Exception);
            exArgs.Handled = true; // 避免直接崩溃(按需)
        };
 
        // 非 UI 线程未处理异常(.NET 4.5+部分场景)
        AppDomain.CurrentDomain.UnhandledException += (_, exArgs) =>
        {
            Log("UnhandledException: " + exArgs.ExceptionObject);
        };
 
        // 进程准备退出(仅“正常路径/有序退出”能走到)
        AppDomain.CurrentDomain.ProcessExit += (_, __) =>
        {
            Log("ProcessExit");
            // 注意:这里已到 very late 阶段,能做的很有限
        };
 
        this.Exit += (_, __) =>
        {
            Log("Application.Exit");
        };
    }
 
    private static void Log(string s)
    {
        // TODO: 写文件/ETW/本地日志;避免 UI 操作
        System.Diagnostics.Debug.WriteLine(s);
    }
}

局限:如果任务管理器硬杀(直接 TerminateProcess),ProcessExit/Exit 都不会来。

所以 要挂 Win32 消息钩子

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
 
public partial class MainWindow : Window
{
    private HwndSource? _source;
 
    public MainWindow()
    {
        InitializeComponent();
 
        this.Closing += (_, e) =>
        {
            // 这里一般是 WM_CLOSE 路径(用户点×、任务管理器尝试关闭)
            Log("Window.Closing");
            // 快速清理/保存;不要耗时太长,避免被系统判定“未响应”
        };
 
        this.Closed += (_, __) => Log("Window.Closed");
    }
 
    protected override void OnStartup(EventArgs e)
    {
        base.OnStartup(e);
        var mainWindow =  Application.Current.Windows;
        _source = (HwndSource)PresentationSource.FromVisual(mainWindow )!;
        _source.AddHook(WndProc);
    }
 
    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        const int WM_QUERYENDSESSION = 0x0011; // 系统要注销/关机的询问
        const int WM_ENDSESSION      = 0x0016; // 注销/关机正式开始
        const int WM_CLOSE           = 0x0010; // 正常关闭窗口(任务管理器结束也用这个)
 
        switch (msg)
        {
            case WM_QUERYENDSESSION:
                Log("WM_QUERYENDSESSION");
                // 返回 false 会阻止注销/关机(很不推荐);默认让系统继续
                break;
 
            case WM_ENDSESSION:
                bool isEnding = wParam != IntPtr.Zero;
                Log("WM_ENDSESSION isEnding=" + isEnding);
                break;
 
            case WM_CLOSE:
                Log("WM_CLOSE");
                // 和 Window.Closing 等价;不要拦很久--但是任务管理器强制关闭回进来一瞬间它会升级为强杀(这时任何事件都不会来)
                break;
        }
        return IntPtr.Zero;
    }
 
    private static void Log(string s) =>
        System.Diagnostics.Debug.WriteLine("[MainWindow] " + s);
}

说明:

任务管理器“结束任务” → 通常先发 WM_CLOSE。如果你不响应或阻塞太久,它会升级为强杀(这时任何事件都不会来)。
关机/注销 → WM_QUERYENDSESSION → WM_ENDSESSION → WM_CLOSE → 退出流程。
能否“知道是任务管理器干的”?
纯粹靠应用内部:不能可靠区分。用户手点 ×、Alt+F4、任务管理器结束,都是 WM_CLOSE。
需要强区分来源:做一个外部看门狗(控制台/服务/另一个进程),用 ManagementEventWatcher/WMI 或轮询监控你的进程。一旦你的进程消失,记录 ExitCode、结合事件日志/安全日志,从外部推断是否为任务管理器/用户操作/崩溃。
基本到此结束。所以挂 Win32 消息钩子也勉强能行,最好加一个监控程序来监控主程序被怎么样干掉。

posted @ 2025-09-09 21:49  FalyEnd  阅读(21)  评论(0)    收藏  举报