More fun Vista API magic

| | Comments (1)

So here's another neat entry for your Win32 API toolbox: application recovery services. Starting with Vista, you can register with the OS to have your application automatically restarted during various crashing situations. What's more, you get the chance to recover data when the application does crash.

It's trivial to implement into your own applications, so I'd highly recommend it. The basic idea is this: when the application starts up, you register a callback which the OS can call when the application crashes. This callback's job is to attempt to recover data from the app which has crashed. Remember -- your application crashed for a reason! So this recovery process can be tricky and you should not make any assumptions about the validity of data.

While you are attempting to recover your data, you must let the OS know that you're really working on something. This way, if your application hangs during the recovery process, the OS can handle it gracefully. Once you're done recovering data, you tell the OS "I recovered data fine|couldn't recover he data."

If you want your application to be automatically restarted, then you can also register that information with the OS as well. You'll tell the OS what command line you wish to be launched with when the application restarts. You can also specify under what circumstances you want to be restarted. For instance, you may not want the application to restart after a hang, but do want it to restart after a crash.

So let's take a look at a contrived example where we recover data and restart the application. We're going to have a window which has an EditField and a PushButton on it. The EditField is going to hold our application "data" and that's what we'll try to recover. The PushButton is what's going to cause the crash.

Half of the interesting stuff is going to happen in the Window1.Open event. When the window opens, we're going to check to see if we've just recovered, and if we have, grabbin our recovery data. Then we're going to register a shared method as our recovery callback and tell the OS we want to restart. It'll look like this:

Sub Open()
dim doWait as Boolean = true

// Check to see if we're a restart run of the application
if InStr( System.CommandLine, "/restarted: " ) > 0 then
// We are, so grab the path to the restart recovery file
dim f as FolderItem = GetFolderItem( NthField( System.CommandLine, "/restarted: ", 2 ) )
if f <> nil then
dim ts as TextInputStream = f.OpenAsTextFile
if ts <> nil then
// Recover our data
EditField1.Text = "Recovered: " + ts.ReadAll

// No need to wait for this run
doWait = false

// Now we can delete the recovery file
ts.Close
f.Delete
end if
end if
end if

// Make our app recovery file name. We do it this way (instead
// of just using GetTemporaryFolderItem) to ensure that the OS
// doesn't delete the folder item when the application closes.
// Otherwise we might make a recovery file which is deleted by the
// OS before we can recover from it. We'll delete the recovery file
// ourselves
dim name as String = GetTemporaryFolderItem.Name
mAppRecoveryPath = TemporaryFolder.Child( name )

Declare Sub RegisterApplicationRecoveryCallback Lib "Kernel32" ( callback as Ptr, param as UInt32, ping as UInt32, flags as UInt32 )
Declare Sub RegisterApplicationRestart Lib "Kernel32" ( cmdLine as WString, flags as UInt32 )

// Register our recovery callback
RegisterApplicationRecoveryCallback( AddressOf RecoveryCallback, 0, 0, 0 )

// Register that we want to be restarted, along with the command line
RegisterApplicationRestart( "/restarted: " + mAppRecoveryPath.AbsolutePath, 0 )

// Fake a wait. The application must run for at least a minute,
// otherwise the OS won't bother restarting the application
if doWait then
App.SleepCurrentThread( 64000 )
end if
End Sub

The other half of the interesting code is going to be in our recovery shared method. Note that we have to use a shared method because all callbacks must be in a shared method or module method (basically: a method with no implicit parameters like me or self).

Shared Function RecoveryCallback(param as UInt32) As UInt32
// This callback method must be a StdCall
#pragma X86CallingConvention StdCall

Declare Sub ApplicationRecoveryInProgress Lib "Kernel32" ( ByRef cancelled as Boolean )
Declare Sub ApplicationRecoveryFinished Lib "Kernel32" ( success as Boolean )

// Tell the OS that we're doing a recovery right now
dim stop as Boolean
ApplicationRecoveryInProgress( stop )
if stop then return 0

// Find the window we want to recover data from
dim wnd as Window1
for i as Integer = 0 to WindowCount - 1
if Window( i ) IsA Window1 then wnd = Window1( Window( i ) )
next i

// Write out to our recovery text file the data we've
// just recovered
wnd.mAppRecoveryPath.CreateTextFile.Write( wnd.EditField1.Text )

// Tell the OS that the recovery happened just fine
ApplicationRecoveryFinished( true )

return 0

Exception
// If anything bad happened, we failed
ApplicationRecoveryFinished( false )
End Function

The only other code we need is mostly helper code. In the Close event, we're going to unregister the application for recovery (since it's no longer needed). And we have to make our crashing code. Those look like this:

Sub Close()
Declare Sub UnregisterApplicationRestart Lib "Kernel32" ()
UnregisterApplicationRestart
End Sub

Sub Action()
dim p as Ptr

p = Ptr( 0 )
p.Int32 = 555
End Sub

Now, if you run the project on Vista, you'll see how well everything works. The application will launch, and after a minute passes, the window will open. You can then type some text into the EditField. Then, press the button to make the application crash. The recovery phase will happen, and eventually your application will launch again with its recovered data shown in the EditField.

Pretty frickin awesome, right? I think so. But there are some annoyances with the API which unfortunately don't seem to be documented very well by MSDN. The first thing which I find annoying is that Windows won't restart your application unless it's been running for at least a minute. It'll still call your recovery function, but it will refuse to restart the application. My guess is that this is because they don't want an application which crashes, then recovers, then crashes immediately and continues in this vicious cycle. But it still makes testing suck. A bigger annoyance is that you cannot register your application to restart from within the recovery callback. This might seem like a silly idea at first blush, but the reason you might want to do that is to set the command line properly. Right now, we have to make a recovery file up-front so that we can register the command line using its path. But if we could set the command line from within the callback, then we would be able to localize the code to just the callback. But alas, it wasn't meant to be (and probably for a valid reason), so we have to do some more legwork up front (or code around the issue).

In any case, I hope you like this little snippet. It was rather fun to learn about it (I happened to stumble on it when looking at some other new Vista APIs). Enjoy!

1 Comments

One other thing to note which I forgot to mention -- you should build the application and run it, not run it from the debugger. Otherwise your re-launch won't work too well. ;-)

Leave a comment

Disclaimer

I'm currently an employee of REAL Software. My blog is mine. The opinions represented in this blog are mine as well and may not represent my employer's opinions. All original material is copyrighted and property of the author.

REALbasic® is a registered trademark of REAL Software, Inc. REAL SQL Server™ and Lingua™ are pending trademarks of REAL Software, Inc. All rights reserved.