本文来告诉大家如何在 Windows 上利用从 Vista 引入的 Windows Error Reporting (WER) 机制来实现,在应用崩溃、无响应等异常的时候收到回调用于处理信息保存

在 《Application Recovery and Restart Reference》 里可以了解到可以通过 Application Recovery and Restart (ARR) 技术,在应用崩溃的时候,有时机可以保存应用的信息。例如做一个类似 Office 的 PPT 的软件,可以在此软件在崩溃的时候,依然有时机可以保存用户的文档信息。从而实现尽可能不会因为软件崩溃而丢失信息

在开始之前,先来做一个演示。如下应用将因为写了逗比代码而无响应,在弹出 WER 时,可以让用户选择重启或退出等。无论选择什么,都可以让应用有机会弹出 应用程序炸掉 提示。换句话说,可以有时机弹出提示,也就是相当于可以做很多保存信息的逻辑,或者说上报的动作,或者制作 DUMP 文件同时上传等

如果用户选择重启的话,还可以在重启的时候将命令行参数发送到重启的应用里面,这样就可以实现在重启的应用里面继续上一个应用的逻辑。例如做的是类似 PPT 的软件,那么在重启之后,依然可以打开原先的文档。这样可以在尽可能在软件没有做好的时候,让用户减少砸桌子

以下是这个逗比应用的代码

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            if (Environment.GetCommandLineArgs().Contains("重启信息"))
            {
                MessageBox.Show("重启");
            }

            Loaded += MainWindow_Loaded;
        }

        private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            var hr = ApplicationCrashRecovery.RegisterApplicationRestart("重启信息", CrashRecoveryApi.RestartFlags.NONE);

            ApplicationCrashRecovery.RegisterApplicationRecoveryCallback();
            ApplicationCrashRecovery.OnApplicationCrashed += ApplicationCrashRecovery_OnApplicationCrashed;

            await Task.Delay(TimeSpan.FromSeconds(3));

            Thread.Sleep(1000000000);
        }

        private void ApplicationCrashRecovery_OnApplicationCrashed()
        {
            // [RegisterApplicationRecoveryCallback function (winbase.h) - Win32 apps | Microsoft Docs](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-registerapplicationrecoverycallback )
            //  By default, the interval is 5 seconds (RECOVERY_DEFAULT_PING_INTERVAL). The maximum interval is 5 minutes. 
            MessageBox.Show("应用程序炸掉");
        }
    }

在启动的时候判断是否有命令行,有的话,就显示命令行的内容。通过以上可以证明可以被用户重启

另外,在 ApplicationCrashRecovery_OnApplicationCrashed 可以弹出提示,证明应用是可以记录更多信息。大概在进入此方法还能使用 5 分钟最多。如果是期望记录 DUMP 文件,可以尝试通过跨进程调用的方法,调用另一个进程辅助记录

本文核心是通过 ARR 的辅助方法,这几个 API 都是 Win32 的方法,可以使用如下代码进行引用

    public class CrashRecoveryApi
    {
        [Flags]
        public enum RestartFlags
        {
            NONE = 0,
            RESTART_NO_CRASH = 1,
            RESTART_NO_HANG = 2,
            RESTART_NO_PATCH = 4,
            RESTART_NO_REBOOT = 8
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        internal static extern int RegisterApplicationRestart(string pwsCommandLine, RestartFlags dwFlags);

        [DllImport("kernel32.dll")]
        internal static extern uint RegisterApplicationRecoveryCallback(IntPtr pRecoveryCallback, IntPtr pvParameter, int dwPingInterval, int dwFlags);

        [DllImport("kernel32.dll")]
        internal static extern uint ApplicationRecoveryInProgress(out bool pbCancelled);

        [DllImport("kernel32.dll")]
        internal static extern uint ApplicationRecoveryFinished(bool bSuccess);

        [DllImport("kernel32.dll")]
        internal static extern uint UnregisterApplicationRecoveryCallback();

        [DllImport("kernel32.dll")]
        internal static extern uint UnregisterApplicationRestart();
    }

以上代码的 ApplicationCrashRecovery 是进一步的封装,如下面代码

    public class ApplicationCrashRecovery
    {
        #region Delegates & Events
        private delegate int ApplicationRecoveryCallback(IntPtr pvParameter);
        public delegate void ApplicationCrashHandler();

        /// <summary>
        /// 监测到发生异常(包括崩溃、卡顿无响应等异常)
        /// </summary>
        public static event ApplicationCrashHandler OnApplicationCrashed;
        #endregion

        private static ApplicationRecoveryCallback _recoverApplication;

        /// <summary>
        /// 向WER注册应用程序重启机制
        /// </summary>
        /// <param name="pwsCommandLine">重启命令行参数</param>
        /// <param name="restartFlag">重启标志,参考RestartFlags</param>
        /// <returns>0表示成功</returns>
        public static int RegisterApplicationRestart(string pwsCommandLine, CrashRecoveryApi.RestartFlags restartFlag)
        {
            if (!IsWindowsVistaOrLater)
            {
                return -1;
            }
            var result = CrashRecoveryApi.RegisterApplicationRestart(pwsCommandLine, restartFlag);
            return result;
        }

        /// <summary>
        /// Registers the application for notification by windows of a failure.
        /// </summary>
        /// <returns>0 if successfully registered for restart notification</returns>
        public static uint RegisterApplicationRecoveryCallback()
        {
            if (!IsWindowsVistaOrLater)
            {
                return 1;
            }
            //  By default, the interval is 5 seconds (RECOVERY_DEFAULT_PING_INTERVAL). The maximum interval is 5 minutes. 
            _recoverApplication = new ApplicationRecoveryCallback(HandleApplicationCrash);
            IntPtr ptrOnApplicationCrash = Marshal.GetFunctionPointerForDelegate(_recoverApplication);
            var result = CrashRecoveryApi.RegisterApplicationRecoveryCallback(ptrOnApplicationCrash, IntPtr.Zero, (int)TimeSpan.FromMinutes(5).TotalMilliseconds,
                0);
            return result;
        }

        /// <returns>0 if successfully unRegistered for restart notification</returns>  
        public static uint UnRegisterForRestart()
        {
            if (!IsWindowsVistaOrLater)
            {
                return 1;
            }
            CrashRecoveryApi.UnregisterApplicationRecoveryCallback();
            var result = CrashRecoveryApi.UnregisterApplicationRestart();
            return result;
        }

        #region Data Persistance Methods
        private static bool IsWindowsVistaOrLater => Environment.OSVersion.Version.Major >= 6;

        /// <summary>
        /// This is the callback function that is executed in the event of the application crashing.
        /// </summary>
        /// <param name="pvParameter"></param>
        /// <returns></returns>
        private static int HandleApplicationCrash(IntPtr pvParameter)
        {
            //Allow the user to cancel the recovery.  The timer polls for that cancel.
            using (System.Threading.Timer t = new System.Threading.Timer(CheckForRecoveryCancel, null, 1000, 1000))
            {
                //Handle this event in your own code
                OnApplicationCrashed?.Invoke();

                CrashRecoveryApi.ApplicationRecoveryFinished(true);
            }

            return 0;
        }

        /// <summary>
        /// Checks to see if the user has cancelled the recovery.
        /// </summary>
        /// <param name="o"></param>
        private static void CheckForRecoveryCancel(object o)
        {
            CrashRecoveryApi.ApplicationRecoveryInProgress(out var userCancelled);

            if (userCancelled)
            {
                Environment.FailFast("User cancelled application recovery");
            }
        }
        #endregion
    }

以上代码也是我从旧项目抄的,也许这个代码也不知道是从哪里抄的,但是大概是可以使用的

此方法的缺点在于如果用户的设备上没有关闭了 WER 那么将无法工作

本文所有代码在 githubgitee 上完全开源

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 4412141e79589e436699f1c84326292afeb273c1

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

获取代码之后,进入 LabujeeneferejurFeqawjewur 文件夹

详细请看 Application Recovery and Restart - Win32 apps


本文会经常更新,请阅读原文: https://blog.lindexi.com/post/WPF-%E5%9F%BA%E4%BA%8E-WER-%E6%B3%A8%E5%86%8C%E5%BA%94%E7%94%A8%E5%B4%A9%E6%BA%83%E6%97%A0%E5%93%8D%E5%BA%94%E5%9B%9E%E8%B0%83%E5%92%8C%E9%87%8D%E5%90%AF%E6%96%B9%E6%B3%95.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者前往 CSDN 关注我的主页

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系

无盈利,不卖课,做纯粹的技术博客

以下是广告时间

推荐关注 Edi.Wang 的公众号

欢迎进入 Eleven 老师组建的 .NET 社区

以上广告全是友情推广,无盈利