As a result of the above changes, how should applications handle shutdown in Windows Vista differently relative to Windows XP? The following are recommended best practices.
Applications should not block shutdown
If you take only one thing away from reading this topic, it should be this one. You will be presenting the best experience to your users if your application does not block shutdown. When users initiate shutdown, in the vast majority of cases, they have a strong desire to see shutdown succeed; they may be in a hurry to leave the office for the weekend, for example. Applications should respect this desire by not blocking shutdown if at all possible.
In many cases, applications that block shutdown can be redesigned so that they no longer need to do so. For example, applications with unsaved data at shutdown can be designed to automatically save their data and restore it when the user next starts the application, instead of blocking shutdown to ask the user what to do. Microsoft Office 2007 will do this in certain scenarios.
In other cases, there is usually a sensible default action that applications should automatically perform, instead of blocking shutdown to ask the user what to do. In many cases, this is true even if the default action could be slightly destructive. For example, if an application is in the middle of burning a CD when shutdown occurs, it should cancel the CD burn instead of blocking shutdown to ask the user what to do. Most users tend not to forget that they have a CD burn in progress, and if they shut down their computers while burning a CD, it would be sensible to assume that they intend to cancel the CD burn. This is what Windows Media Player 11 will do if the user shuts down while a CD burn is in progress.
Applications that must block shutdown should use the new shutdown reason API
In Windows XP, Microsoft recommended that if an application needed to block shutdown, it should display its own UI explaining why it needed to do so, so users would be less frustrated when shutdown failed. As discussed earlier, Windows Vista will reduce user frustration when shutdown fails even more, by displaying new UI that lists all the reasons applications have provided for blocking shutdown.
In Windows Vista, if your application must block shutdown, in addition to returning FALSE or not responding to WM_QUERYENDSESSION, it should leverage this new UI by using a simple API to provide Windows with a reason string explaining why it is blocking shutdown. This API is straightforward:
BOOL ShutdownBlockReasonCreate(HWND hWnd, LPCWSTR pwszReason);
BOOL ShutdownBlockReasonDestroy(HWND hWnd);
BOOL ShutdownBlockReasonQuery(HWND hWnd, LPWSTR pwszBuff, DWORD *pcchBuff);
Use of this API is detailed more fully later in this topic, as well as in the MSDN documentation for the individual ShutdownBlockReason() functions.
Again, note that this API does not replace the need to return FALSE (or delay responding) to WM_QUERYENDSESSION to block shutdown. Applications need to do this in addition to using the API. Applications that return TRUE to WM_QUERYENDSESSION will be closed at shutdown, regardless of whether they have used the API.
Also note that if your application has no visible top-level windows, it must use this API if it needs to successfully block shutdown. Such applications will automatically be terminated if they block shutdown without using the API.
Applications should no longer rely on always being able to block shutdown
If the user initiates a critical shutdown by clicking the "Shut down now" button from the new Windows UI discussed earlier, applications will not be allowed to block shutdown. In a critical shutdown, applications that return FALSE to WM_QUERYENDSESSION will be sent WM_ENDSESSION and closed, while those that time out in response to WM_QUERYENDSESSION will be terminated. The different procedure that is followed in a critical shutdown is summarized in Table 2 above.
In most cases, shutdown will not be critical, but applications should be prepared for the possibility of a critical shutdown. By following these recommendations, you can ensure that your application will handle critical shutdowns gracefully, without being terminated:
- Make sure your application is prepared to respond to WM_ENDSESSION even if it responded FALSE to WM_QUERYENDSESSION.
- Make sure your application responds to WM_QUERYENDSESSION as quickly as possible. If you need to do any time-consuming processing at shutdown, you should do it in response to WM_ENDSESSION. As indicated in Table 2, applications that respond TRUE to WM_QUERYENDSESSION will by default be given 30 seconds to do shutdown processing and respond to WM_ENDSESSION.
- If your application needs to determine whether a shutdown is critical, it can check the ENDSESSION_CRITICAL bit in the lParam parameter of WM_QUERYENDSESSION.
Using the New Shutdown Reason API
The new shutdown reason API consists of three functions:
BOOL ShutdownBlockReasonCreate(HWND hWnd, LPCWSTR pwszReason);
BOOL ShutdownBlockReasonDestroy(HWND hWnd);
BOOL ShutdownBlockReasonQuery(HWND hWnd, LPWSTR pwszBuff, DWORD *pcchBuff);
Again, the best practice for Windows Vista applications at shutdown is that they should never block shutdown. However, if your application must block shutdown, Microsoft recommends that you use this API. The functions are simple to use.
- When your application needs to block shutdown, it should call ShutdownBlockReasonCreate() to register a reason string, and pass in a handle to the window it uses to handle WM_QUERYENDSESSION.
- When your application no longer needs to block shutdown, it should call ShutdownBlockReasonDestroy() to unregister its reason string.
- If your application needs to determine what reason string it registered earlier, it should call ShutdownBlockReasonQuery() to retrieve it.
Windows will store the reason string associated with your application for the duration of the user's session. The reason string store is not persisted when the user successfully shuts down; new sessions initialize an empty reason string store.
There are three main usage models for these functions.
- The application knows ahead of time when it will need to block shutdown (preferred usage model)
- The application only knows whether it will need to block shutdown during the shutdown process
- The application has no visible top-level windows and needs more than 30 seconds to handle WM_ENDSESSION and perform shutdown processing
These are described in detail below.
The application knows ahead of time when it will need to block shutdown (preferred usage model)
An example of this is an application that performs a critical operation and knows that it will need to block shutdown during that operation. If that operation is performed in a single block of code within the application, then ShutdownBlockReasonCreate() should be called at the start of that block of code, and ShutdownBlockReasonDestroy() at the end. In addition, the application should return FALSE to WM_QUERYENDSESSION if the operation is in progress when WM_QUERYENDSESSION is received.
This way, the application will block shutdown and its reason for blocking shutdown will be displayed if and only if the critical operation was in progress when shutdown was initiated.
This is the preferred usage model for this API, because it has the advantage that if another application blocks shutdown before your application and the new Windows UI is displayed, your application will be listed as needing to block shutdown and its reason string will be shown. This is not true for the other usage models discussed below.
The application only knows whether it will need to block shutdown during the shutdown process
In some cases, an application can only know whether it will need to block shutdown when it is responding to WM_QUERYENDSESSION. In this case, it should call ShutdownBlockReasonCreate() within its WM_QUERYENDSESSION handler, then respond FALSE to WM_QUERYENDSESSION.
This way, the application will block shutdown and its reason for blocking shutdown will be displayed.
There are two important points to note in this usage model:
- It is especially important to ensure that your application unregisters its reason string by calling ShutdownBlockReasonDestroy() later, when it no longer needs to block shutdown. Otherwise, Windows may display a stale string for your application if a future shutdown is blocked, and your users may be confused.
- Your application will not always be presented to the user as needing to block shutdown. Specifically, if another application blocks shutdown before your application and the new Windows UI is displayed, your application will not be identified as needing to block shutdown, because it will not have been sent WM_QUERYENDSESSION yet.
The application has no visible top-level windows and needs more than 30 seconds to handle WM_ENDSESSION and perform shutdown processing
By default, applications without any visible top-level windows will be given 5 seconds to handle WM_ENDSESSION before being terminated.
If your application may need more than 5 seconds to complete its shutdown processing in response to WM_ENDSESSION, it should call ShutdownBlockReasonCreate() in its WM_QUERYENDSESSION handler, and promptly respond TRUE to WM_QUERYENDSESSION so as not to block shutdown. It should then perform all shutdown processing in its WM_ENDSESSION handler.
This way, Windows will treat your application as if it had visible top-level windows and will give it 30 seconds to handle WM_ENDSESSION.
Best Practices For Shutdown Reason Strings
When users are shutting down Windows, they are typically in a hurry and will spend just a few seconds looking at the new Windows UI listing application shutdown reasons. Because of this, it is imperative that the reason strings your application registers are easy to read and understand.
Below are examples of reason strings, as well as revisions to those strings that make them more concise and therefore easier to understand.
Less Clear | Clear |
---|---|
This application is blocking shutdown because a CD burn is in progress. | A CD burn is in progress. |
A TV show recording is currently occurring. | A TV show is being recorded. |
A transaction is in progress. Do not shut down. | A transaction is in progress. |