Vista Application Recovery
Ever want to add the ability to recover the user data if your application has crashed. Windows Vista has some nice apis to make this easy.
The RegisterApplicationRestart api tells Vista we would like the app to restart if it crashes or hangs. This api takes two arguments a string which will be the command line argument when the application restarts and RestartFlags. For this example we will tell the app to always restart. Note the app must run 60 seconds before it will restart.
Enum RestartFlags
Always = 0
Cyclical = 1
NotifySolution = 2
NotifyFault = 4
DoNotRestartOnCrash = 8
DoNotRestartOnHang = 16
DoNotRestartOnUpdate = 32
End Enum
Declare Auto Function RegisterApplicationRestart Lib "kernel32.dll" (ByVal strMessage As String, ByVal RestartOption As RestartFlags) As Integer
RegisterApplicationRestart("Restarted", RestartFlags.Always)
The RegisterApplicationRecoveryCallback will allow us to register a function to run if our application crashes or hangs. This will allow us to save the users work before the app terminates. Note that you will not have access to the forms controls but you will have access to the variables. If you want to be able to recover the text in a textbox I would use a variable which is updated in the textboxes textchanged event. Inside the callback function you must tell Vista you are working with the ApplicationRecoveryInProgress function. When the recovery is complete tell Vista with the ApplicationRecoveryFinished function.
Declare Auto Function RegisterApplicationRecoveryCallback Lib "kernel32.dll" (ByVal cb As App_Recovery_Callback, ByVal Param As String, ByVal PintInterval As Integer, ByVal flags As RestartFlags) As Integer
Declare Auto Function ApplicationRecoveryInProgress Lib "kernel32.dll" (ByRef Canceled As Boolean) As Integer
Declare Auto Function ApplicationRecoveryFinished Lib "kernel32.dll" (ByRef Success As Boolean) As Integer
Delegate Function App_Recovery_Callback(ByVal Param As String) As Integer
Private Function Recover(ByVal Param As String) As Integer
Dim b As Boolean
Using sw As New StreamWriter("Recovered.txt", False)
ApplicationRecoveryInProgress(b)
sw.WriteLine(strTextbox)
ApplicationRecoveryInProgress(b)
sw.Flush()
sw.Close()
End Using
ApplicationRecoveryFinished(True)
Return 0
End Function
One last thing. The .Net framework will catch the error and prevent our recovery callback function from being called. So we need to uncheck the enable application framework box in my projects so we can make the application start with Sub Main. In Sub Main we can Set the UnHandledException Mode to throw exception so Vista will know about the crash
Public Shared Sub Main()
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException)
Application.Run(New Form1)
End Sub
For the sample Application I have a form with a label (label1), textbox (textbox1), 2 buttons (btnCrash an btnHang), and a timer. The timer is used to update the label with how long the app ran. The 2 buttons will hang or crash the app. Here is the code for the sample app. For the app recovery to work run the program without debugging (ctrl+F5).
Imports System.IO
Public Class Form1
Enum RestartFlags
Always = 0
Cyclical = 1
NotifySolution = 2
NotifyFault = 4
DoNotRestartOnCrash = 8
DoNotRestartOnHang = 16
DoNotRestartOnUpdate = 32
End Enum
Dim strTextbox As String
Declare Auto Function RegisterApplicationRestart Lib "kernel32.dll" (ByVal strMessage As String, ByVal RestartOption As RestartFlags) As Integer
Declare Auto Function RegisterApplicationRecoveryCallback Lib "kernel32.dll" (ByVal cb As App_Recovery_Callback, ByVal Param As String, ByVal PintInterval As Integer, ByVal flags As RestartFlags) As Integer
Declare Auto Function ApplicationRecoveryInProgress Lib "kernel32.dll" (ByRef Canceled As Boolean) As Integer
Declare Auto Function ApplicationRecoveryFinished Lib "kernel32.dll" (ByRef Success As Boolean) As Integer
Delegate Function App_Recovery_Callback(ByVal Param As String) As Integer
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Static x As Integer = 0
Label1.Text = String.Format("App running {0} seconds", x)
x += 1
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
If My.Application.CommandLineArgs.Count > 0 Then
TextBox1.Text = My.Computer.FileSystem.ReadAllText("Recovered.txt")
ApplicationRecoveryFinished(True)
End If
'register app to restart
RegisterApplicationRestart("Restarted", RestartFlags.Always)
'setup the Recovery Callback
RegisterApplicationRecoveryCallback(AddressOf Recover, "Recover Data", 50000, RestartFlags.Always)
'start a timer to show how long the app has run
Timer1.Interval = 1000
Timer1.Enabled = True
End Sub
Private Function Recover(ByVal Param As String) As Integer
Dim b As Boolean
Using sw As New StreamWriter("Recovered.txt", False)
ApplicationRecoveryInProgress(b)
sw.WriteLine(strTextbox)
ApplicationRecoveryInProgress(b)
sw.Flush()
sw.Close()
End Using
ApplicationRecoveryFinished(True)
Return 0
End Function
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCrash.Click
Throw New Exception("Crash Me!!!")
End Sub
Public Shared Sub Main()
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException)
Application.Run(New Form1)
End Sub
Private Sub TextBox1_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged
strTextbox = TextBox1.Text
End Sub
Private Sub btnHang_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnHang.Click
Do While True
' lock me up
Loop
End Sub
End Class
Daniel Moth and Bart De Smet's have some nice blog entries about using app recovery with c# console apps. You can register your application (a digital signature is required) at the Windows Quality Online Services site to be able to see any error reports sent by your app.