vista如何阻断用户关机过程
Introduction
Did I tell you already how great Windows Vista is? I guess so. Today, I'm showing you yet another piece of Windows Vista API goodness: ShutdownBlockReasonCreate. The goal of this function (defined in user32.dll) is to block system shutdown with a friendly message. This is handy if your application is doing a critical operation, such as writing to a cd or dvd. But I guess you can find other good reasons too. Check out the Application Shutdown Changes in Windows Vista article on MSDN too.
The code
Window messages
In order to understand what's happening, you need to know a little bit about window messages. Basically, the OS sends messages to an application to tell it about mouse moves, etc. In the winuser.h file (that comes with the Windows SDK) you can find all sorts of window events, prefixed by WM_. Two of these messages are WM_QUERYENDSESSION and WM_ENDSESSION. A few definitions:
The WM_QUERYENDSESSION message is sent when the user chooses to end the session or when an application calls one of the system shutdown functions. If any application returns zero, the session is not ended. The system stops sending WM_QUERYENDSESSION messages as soon as one application returns zero.
The WM_ENDSESSION message is sent to an application after the system processes the results of the WM_QUERYENDSESSION message. The WM_ENDSESSION message informs the application whether the session is ending.
The former window message is interesting to us because it allows to avoid shutdown: if any application returns zero, the session is not ended. In the past, this would lead to a situation where Windows asks to kill the applications ("not responding"). In Windows Vista, one can supply shutdown blocking reasons which are displayed by Windows Vista is a user-friendly way.
Capturing window messages
First I need to show you how to capture window messages in a Windows Forms app. Assume a one-form Windows Forms application and go to Form1.cs. In there (or more generally, in the app's main window), override the WndProc method:
private bool blocked = false;
protected override void WndProc(ref Message aMessage)
{
const int WM_QUERYENDSESSION = 0x0011;
const int WM_ENDSESSION = 0x0016;
if (blocked && (aMessage.Msg == WM_QUERYENDSESSION || aMessage.Msg == WM_ENDSESSION))
return;
base.WndProc(ref aMessage);
}
protected override void WndProc(ref Message aMessage)
{
const int WM_QUERYENDSESSION = 0x0011;
const int WM_ENDSESSION = 0x0016;
if (blocked && (aMessage.Msg == WM_QUERYENDSESSION || aMessage.Msg == WM_ENDSESSION))
return;
base.WndProc(ref aMessage);
}
Don't forget the base call; otherwise Win32Exceptions will occur and the form won't even be created.
What this code does is blocking message forwarding (by the base call) when the WM_QUERYENDSESSION (or WM_ENDSESSION) message is received and the blocking is turned on (by means of a variable blocked). Let's see when we turn this variable on now.
A shutdown blocking reason
Add the following controls to the form of our demo application:
- TextBox txtBox1 - used to supply a shutdown reason
- Button button1 with caption "Block" - used to turn on shutdown blocking
- Button button1 with caption "Unblock" - used to turn off shutdown blocking
The result looks like this:
On to the code:
private void button1_Click(object sender, EventArgs e)
{
if (ShutdownBlockReasonCreate(this.Handle, textBox1.Text))
{
blocked = true;
MessageBox.Show("Shutdown blocking succeeded");
}
else
MessageBox.Show("Shutdown blocking failed");
}
private void button2_Click(object sender, EventArgs e)
{
if (ShutdownBlockReasonDestroy(this.Handle))
{
blocked = false;
MessageBox.Show("Shutdown unblocking succeeded");
}
else
MessageBox.Show("Shutdown unblocking failed");
}
{
if (ShutdownBlockReasonCreate(this.Handle, textBox1.Text))
{
blocked = true;
MessageBox.Show("Shutdown blocking succeeded");
}
else
MessageBox.Show("Shutdown blocking failed");
}
private void button2_Click(object sender, EventArgs e)
{
if (ShutdownBlockReasonDestroy(this.Handle))
{
blocked = false;
MessageBox.Show("Shutdown unblocking succeeded");
}
else
MessageBox.Show("Shutdown unblocking failed");
}
Two Windows Vista API functions are called:
[DllImport("user32.dll")]
public extern static bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string pwszReason);
[DllImport("user32.dll")]
public extern static bool ShutdownBlockReasonDestroy(IntPtr hWnd);
public extern static bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string pwszReason);
[DllImport("user32.dll")]
public extern static bool ShutdownBlockReasonDestroy(IntPtr hWnd);
The first one is used to supply a friendly shutdown blocking message. The second one is used to destroy it again. The first parameter to both functions is the window handle of the main window.
Tip: Don't forget to set the marshal type of the pwszReason parameter, otherwise you'll get Chinese-looking characters.
The demo
Compile the application in a Release build mode. Now run it, type a shutdown reason, and click Block:
Now try to log off the machine. The screen will go dark and the shutdown blocking reason will be displayed (click to enlarge):
(Don't worry about the SysFader thing, that's an issue related to Nvidia stuff on my machine.)
If you click Unblock again and then try to log off, the app won't block the operation anymore.
Conclusion
Yet another great Windows Vista feature, but use it with care. Only if your app can't afford to be shut down in the middle of some critical operation, you should consider to use this API. In fact, the ShutdownBlockReason* set of API functions is not required to get this to work. You can just choose not to process the WM_QUERYENDSESSION window message and things will be blocked. However, a user-friendly message yields a better end-user experience and that's where ShutdownBlockReasonCreate comes into play.
Have fun (once more)!
翻译: