本文来告诉大家如何在 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 那么将无法工作
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 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 文件夹
本文会经常更新,请阅读原文: 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订阅博客,或者收藏我的博客导航
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。
无盈利,不卖课,做纯粹的技术博客
以下是广告时间
推荐关注 Edi.Wang 的公众号
欢迎进入 Eleven 老师组建的 .NET 社区
以上广告全是友情推广,无盈利