调用'ShutdownBlockReasonCreate'函数不会阻止用户关闭系统
Calling 'ShutdownBlockReasonCreate' function does not prevent the user from shutting down the system
This文章说:
If an application must block a potential system shutdown, it can call
the ShutdownBlockReasonCreate function. The caller provides a reason
string that will be displayed to the user.
并且在 ShutdownBlockReasonCreate 文档中明确指出,当尝试关闭时,将向用户显示带有原因字符串的对话框 window:
Indicates that the system cannot be shut down and sets a reason string
to be displayed to the user if system shutdown is initiated
并且 window 对话框的出现在 this 讨论中得到确认:
The user could click "Shut down anyway". Besides, the system assumes
"Shut down anyway" if the user takes no action within some number of
seconds.
然而,在我调用 ShutdownBlockReasonCreate 传递当前应用程序的主 window 句柄后,确保函数成功并通过调用 [=20= 双重确保它] 函数来检索原因字符串,它不会阻止用户关闭系统并且不会显示对话框 window。
为什么它对我的系统没有影响?我该如何解决这个问题?
我 运行 在 Windows 10 x64 上使用管理员(内置)帐户,我使用的代码来自 this GitHub 资料库:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Vanara.PInvoke;
using static Vanara.PInvoke.User32;
namespace Vanara.Windows.Forms.Forms
{
/// <summary>Used to define a set of operations within which any shutdown request will be met with a reason why this application is blocking it.</summary>
/// <remarks>This is to be used in either a 'using' statement or for the life of the application.
/// <para>To use for the life of the form, define a class field:
public class PreventShutdownContext : IDisposable
{
private HandleRef href;
/// <summary>Initializes a new instance of the <see cref="PreventShutdownContext"/> class.</summary>
/// <param name="window">The <see cref="Form"/> or <see cref="Control"/> that contains a valid window handle.</param>
/// <param name="reason">The reason the application must block system shutdown. Because users are typically in a hurry when shutting down the system, they may spend only a few seconds looking at the shutdown reasons that are displayed by the system. Therefore, it is important that your reason strings are short and clear.</param>
public PreventShutdownContext(Control window, string reason)
{
href = new HandleRef(window, window.Handle);
Reason = reason;
}
/// <summary>The reason the application must block system shutdown. Because users are typically in a hurry when shutting down the system, they may spend only a few seconds looking at the shutdown reasons that are displayed by the system. Therefore, it is important that your reason strings are short and clear.</summary>
/// <value>The reason string.</value>
public string Reason
{
get
{
if (!ShutdownBlockReasonQuery(href.Handle, out var reason))
Win32Error.ThrowLastError();
return reason;
}
set
{
if (value == null) value = string.Empty;
if (ShutdownBlockReasonQuery(href.Handle, out var _))
ShutdownBlockReasonDestroy(href.Handle);
if (!ShutdownBlockReasonCreate(href.Handle, value))
Win32Error.ThrowLastError();
}
}
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
ShutdownBlockReasonDestroy(href.Handle);
}
}
}
...
[DllImport(Lib.User32, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShutdownBlockReasonCreate(HWND hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason);
[DllImport(Lib.User32, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShutdownBlockReasonQuery(HWND hWnd, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszBuff, ref uint pcchBuff);
[DllImport(Lib.User32, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShutdownBlockReasonDestroy(HWND hWnd);
像这样的用法:
using (new PreventShutdownContext(this, "This app is super busy right now."))
{
// Do something that can't be interrupted...
}
我按原样尝试了代码,其中包含 P/Invoke 定义,并对我使用 IntPtr 结构的代码进行了一些修改 window 句柄而不是自定义 HWND 结构,并将我在上面评论中指定的应用程序的主 window 句柄传递给它。
这是设计使然。
documentation(以及您引用的主题)可能有点误导。
Indicates that the system cannot be shut down and sets a reason string to be displayed to the user if system shutdown is initiated.
If an application must block a potential system shutdown, it can call the ShutdownBlockReasonCreate
function.
这个函数实际上只为您的应用程序设置消息字符串。此功能不会防止您的应用程序被关闭。
要实施关闭块,只需按照您引用的文章中描述的步骤操作即可。您需要对 WM_QUERYENDSESSION
消息和 return FALSE
(0) 做出反应。
作为参考,另请参阅 WM_QUERYENDSESSION
文档。
您可能还会发现有趣的东西 this topic - 它描述了 Windows Vista 引入的变化,并包含如何实现关闭逻辑的最佳实践。
顺便说一句,关于您的应用程序不会有特殊的“对话window”。将显示标准 Windows 关闭 UI(它因 OS 版本而异)。您的应用程序将出现在 “防止关机的应用程序” 列表中,其中包含您使用 ShutdownBlockReasonCreate
功能注册的消息 - 但前提是 returns FALSE
用于 WM_QUERYENDSESSION
消息。
更新
如果上述解决方案(WM_QUERYENDSESSION
)未能解决问题,可能是系统设置忽略了此机制所致。
@ElektroStudios 在他们的研究中发现:
- 如果用户设置了
AutoEndTasks
注册表值(在 HKCU\Control Panel\Desktop
注册表项中找到),则关机不会显示任何 UI 让用户取消关机。所以在这些情况下创建“取消关闭原因”是没有用的,因为应用程序无论如何都会被强制立即关闭(以继续关闭)。作为参考,阅读 this MS Docs topic.
- 为了使这个东西按预期工作,
AutoEndTasks
注册表值必须为 0(零);否则,任何试图阻止关机的应用程序都将被终止,并且不会在关机时显示 UI。
- 可以将
AutoEndTasks
值添加到覆盖 HKCU
配置单元和 HKU\{SID}
中定义的值的 HKEY_USERS\.DEFAULT\Control Panel\Desktop
键。这意味着,如果 AutoEndTasks
在 HKCU
中是 false
(0) 但在 HKU\.DEFAULT
中是 true
(1),则应用程序不会阻止系统shutting down and no shutdown UI 将显示。如果 AutoEndTasks
在 HKU\.DEFAULT
中为 false
但在 HKCU
中为真,则应用程序将阻止系统关闭并显示关闭 UI。
- 另外一个好处是
AutoEndTasks
值不需要 restart/log-off 系统生效。因此,一旦在适当的键中设置为 false
(例如 HKEY_USERS\.DEFAULT\Control Panel\Desktop
),应用程序将阻止系统关闭,我们可以在使用完后将此值恢复到之前的状态功能。
如果有人感兴趣,我将在 VB.NET 中分享我的自定义实现。目前这段代码缺少 Windows API 定义和我用来 read/write 到 AutoEndTasks 注册表值的方法(成员name TweakingUtil.AutoEndTasks
在下面的代码中),但你可以得到我认为最重要的想法...
Imports Microsoft.Win32
Imports System.Security
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Provides a mechanism to prevent any system shutdown/restart/log-off request during the life-cycle of a instance of this class.
''' <para></para>
''' Applications should use this class as they begin an operation that cannot be interrupted, such as burning a CD or DVD.
''' <para></para>
''' This class is to be used in either a <see langword="Using"/> statement or for the life-cycle of the current application.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <remarks>
''' Original source-code: <see href="https://github.com/dahall/Vanara/blob/master/WIndows.Forms/Contexts/PreventShutdownContext.cs"/>
''' </remarks>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code lang="vb">
''' Using psc As New PreventShutdownContext("Critical operation is in progress...")
''' ' Do something that can't be interrupted...
''' End Using
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code lang="vb">
''' Public NotInheritable Class Form1 : Inherits Form
'''
''' Private psc As PreventShutdownContext
'''
''' Private Sub AllowShutdown()
''' If (Me.psc IsNot Nothing) Then
''' Me.psc.Dispose()
''' Me.psc = Nothing
''' End If
''' End Sub
'''
''' Private Sub DisallowShutdown()
''' If (Me.psc Is Nothing) Then
''' Me.psc = New PreventShutdownContext("Application defined reason goes here.")
''' End If
''' End Sub
'''
''' Protected Overrides Sub OnShown(ByVal e As EventArgs)
''' Me.DisallowShutdown()
''' MyBase.OnShown(e)
''' End Sub
'''
''' End Class
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example using the <see langword="using"/> statement.
''' <code lang="cs">
''' using (new PreventShutdownContext("Critical operation is in progress...")) {
''' // Do something that can't be interrupted...
''' }
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <code lang="cs">
''' public partial class Form1 : Form {
'''
''' private PreventShutdownContext disallowShutdown;
'''
''' private void AllowShutdown() {
''' if (this.psc != null) {
''' this.psc.Dispose();
''' this.psc = null;
''' }
''' }
'''
''' private void DisallowShutdown() {
''' if (this.psc == null) {
''' this.psc = new PreventShutdownContext("Application defined reason goes here.");
''' }
''' }
'''
''' protected override void OnShown(EventArgs e) {
''' this.DisallowShutdown();
''' base.OnShown(e);
''' }
'''
''' }
''' </code>
''' ----------------------------------------------------------------------------------------------------
Public NotInheritable Class PreventShutdownContext : Implements IDisposable
#Region " Private Fields "
''' <summary>
''' Holds the main window handle for the current application.
''' </summary>
Private ReadOnly hRef As HandleRef
''' <summary>
''' Flag to determine whether the shutdown reason is created.
''' </summary>
Private isReasonCreated As Boolean
''' <summary>
''' Holds the previous value of "HKEY_USERS\.DEFAULT\Control Panel\Desktop" "AutoEndTasks" registry value.
''' <para></para>
''' This registry value is restored when calling <see cref="PreventShutdownContext.Dispose()"/>
''' </summary>
Private ReadOnly previousAutoEndTasksValue As Boolean
#End Region
#Region " Constructors "
''' <summary>
''' Initializes a new instance of the <see cref="PreventShutdownContext"/> class.
''' </summary>
''' <param name="reason">
''' The reason for which the current application must prevent system shutdown.
''' <para></para>
''' Because users are typically in a hurry when shutting down the system,
''' they may spend only a few seconds looking at the shutdown reasons that are displayed by the system.
''' Therefore, it is important that your reason strings are short and clear.
''' </param>
'''
''' <param name="throwOnError">
''' If <see langword="True"/>, an exception will be thrown if
''' the application does not meet the requirements to prevent a system shutdown.
''' <para></para>
''' Default value is <see langword="True"/>.
''' </param>
''' <exception cref="InvalidOperationException">
''' Applications without a user interface can't prevent a system shutdown.
''' </exception>
'''
''' <exception cref="InvalidOperationException">
''' The main window of the current application is not yet created or is not visible.
''' </exception>
'''
''' <exception cref="InvalidOperationException">
''' Only the thread that created the main window of the current application can call this to prevent a system shutdown.
''' </exception>
'''
''' <exception cref="SecurityException">
''' The user does not have the permissions required to create or modify 'AutoEndTasks' registry value.
''' Therefore, the application can't prevent a system shutdown.
''' </exception>
<DebuggerStepThrough>
Public Sub New(ByVal reason As String, Optional ByVal throwOnError As Boolean = True)
If Not Environment.UserInteractive Then
If (throwOnError) Then
Throw New InvalidOperationException(
"Applications without a user interface can't prevent a system shutdown.")
End If
End If
Dim pr As Process = Process.GetCurrentProcess()
Me.hRef = New HandleRef(pr, pr.MainWindowHandle)
If (Me.hRef.Handle = IntPtr.Zero) AndAlso (throwOnError) Then
Throw New InvalidOperationException(
"The main window of the current application is not yet created or is not visible.")
End If
Dim currentThreadId As UInteger = NativeMethods.GetCurrentThreadId()
Dim mainThreadId As Integer = NativeMethods.GetWindowThreadProcessId(Me.hRef.Handle, Nothing)
If (currentThreadId <> mainThreadId) AndAlso (throwOnError) Then
Throw New InvalidOperationException(
"Only the thread that created the main window of the current application can call this to prevent a system shutdown.")
End If
Me.previousAutoEndTasksValue = TweakingUtil.AutoEndTasks
If (Me.previousAutoEndTasksValue) Then
Try
TweakingUtil.AutoEndTasks = False
Catch ex As SecurityException
If (throwOnError) Then
Throw New SecurityException(
"The user does not have the permissions required to create or modify 'AutoEndTasks' registry value. " &
"Therefore, the application can't prevent a system shutdown.", ex)
End If
Catch ex As Exception
If (throwOnError) Then
Throw
End If
End Try
End If
AddHandler SystemEvents.SessionEnding, AddressOf Me.SessionEnding
Me.Reason = reason
End Sub
#End Region
#Region " Properties "
''' <summary>
''' Gets or sets the reason for which the current application must prevent system shutdown.
''' <para></para>
''' Because users are typically in a hurry when shutting down the system,
''' they may spend only a few seconds looking at the shutdown reasons that are displayed by the system.
''' Therefore, it is important that your reason strings are short and clear.
''' </summary>
''' <value>
''' The reason for which the current application must prevent system shutdown.
''' </value>
Public Property Reason As String
Get
Return Me.reason_
End Get
<DebuggerStepThrough>
Set(ByVal value As String)
If value.Equals(Me.reason_, StringComparison.Ordinal) Then
Exit Property
End If
Me.SetReason(value)
Me.reason_ = value
End Set
End Property
''' <summary>
''' ( backing field of <see cref="PreventShutdownContext.Reason"/> property )
''' <para></para>
''' The reason for which the application must prevent system shutdown.
''' </summary>
Private reason_ As String
#End Region
#Region " Event-Handlers "
''' <summary>
''' Handles the <see cref="Microsoft.Win32.SystemEvents.SessionEnding"/> event.
''' </summary>
''' <param name="sender">
''' The source of the event.
''' </param>
'''
''' <param name="e">
''' The <see cref="SessionEndingEventArgs"/> instance containing the event data.
''' </param>
Private Sub SessionEnding(ByVal sender As Object, e As SessionEndingEventArgs)
' By setting "e.Cancel" property to True,
' the application will respond 0 (zero) to "WM_QUERYENDSESSION" message in order to prevent a system shutdown.
'
' For more info:
' https://docs.microsoft.com/en-us/windows/desktop/shutdown/wm-queryendsession
' https://docs.microsoft.com/en-us/windows/desktop/Shutdown/shutdown-changes-for-windows-vista
e.Cancel = True
End Sub
#End Region
#Region " Private Methods "
''' <summary>
''' Sets the reason for which the current application must prevent system shutdown.
''' </summary>
''' <param name="reason">
''' The reason for which the current application must prevent system shutdown.
''' <para></para>
''' Because users are typically in a hurry when shutting down the system,
''' they may spend only a few seconds looking at the shutdown reasons that are displayed by the system.
''' Therefore, it is important that your reason strings are short and clear.
''' </param>
''' <exception cref="Win32Exception">
''' </exception>
<DebuggerStepThrough>
Private Sub SetReason(ByVal reason As String)
Dim succeed As Boolean
Dim win32Err As Integer
If (Me.isReasonCreated) Then
succeed = NativeMethods.ShutdownBlockReasonDestroy(Me.hRef.Handle)
win32Err = Marshal.GetLastWin32Error()
If Not succeed Then
Throw New Win32Exception(win32Err)
End If
End If
succeed = NativeMethods.ShutdownBlockReasonCreate(Me.hRef.Handle, reason)
win32Err = Marshal.GetLastWin32Error()
If Not succeed Then
Throw New Win32Exception(win32Err)
End If
Me.isReasonCreated = True
End Sub
#End Region
#Region " IDisposable Implementation "
''' <summary>
''' Flag to detect redundant calls when disposing.
''' </summary>
Private isDisposed As Boolean
''' <summary>
''' Releases all the resources used by this instance.
''' </summary>
<DebuggerStepThrough>
Public Sub Dispose() Implements IDisposable.Dispose
Me.Dispose(isDisposing:=True)
End Sub
''' <summary>
''' Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
''' Releases unmanaged and, optionally, managed resources.
''' </summary>
''' <param name="isDisposing">
''' <see langword="True"/> to release both managed and unmanaged resources;
''' <see langword="False"/> to release only unmanaged resources.
''' </param>
<DebuggerStepThrough>
Private Sub Dispose(ByVal isDisposing As Boolean)
If (Not Me.isDisposed) AndAlso (isDisposing) Then
RemoveHandler SystemEvents.SessionEnding, AddressOf Me.SessionEnding
NativeMethods.ShutdownBlockReasonDestroy(Me.hRef.Handle)
Me.isReasonCreated = False
Try
TweakingUtil.AutoEndTasks = Me.previousAutoEndTasksValue
Catch
End Try
End If
Me.isDisposed = True
End Sub
#End Region
End Class
This文章说:
If an application must block a potential system shutdown, it can call the ShutdownBlockReasonCreate function. The caller provides a reason string that will be displayed to the user.
并且在 ShutdownBlockReasonCreate 文档中明确指出,当尝试关闭时,将向用户显示带有原因字符串的对话框 window:
Indicates that the system cannot be shut down and sets a reason string to be displayed to the user if system shutdown is initiated
并且 window 对话框的出现在 this 讨论中得到确认:
The user could click "Shut down anyway". Besides, the system assumes "Shut down anyway" if the user takes no action within some number of seconds.
然而,在我调用 ShutdownBlockReasonCreate 传递当前应用程序的主 window 句柄后,确保函数成功并通过调用 [=20= 双重确保它] 函数来检索原因字符串,它不会阻止用户关闭系统并且不会显示对话框 window。
为什么它对我的系统没有影响?我该如何解决这个问题?
我 运行 在 Windows 10 x64 上使用管理员(内置)帐户,我使用的代码来自 this GitHub 资料库:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Vanara.PInvoke;
using static Vanara.PInvoke.User32;
namespace Vanara.Windows.Forms.Forms
{
/// <summary>Used to define a set of operations within which any shutdown request will be met with a reason why this application is blocking it.</summary>
/// <remarks>This is to be used in either a 'using' statement or for the life of the application.
/// <para>To use for the life of the form, define a class field:
public class PreventShutdownContext : IDisposable
{
private HandleRef href;
/// <summary>Initializes a new instance of the <see cref="PreventShutdownContext"/> class.</summary>
/// <param name="window">The <see cref="Form"/> or <see cref="Control"/> that contains a valid window handle.</param>
/// <param name="reason">The reason the application must block system shutdown. Because users are typically in a hurry when shutting down the system, they may spend only a few seconds looking at the shutdown reasons that are displayed by the system. Therefore, it is important that your reason strings are short and clear.</param>
public PreventShutdownContext(Control window, string reason)
{
href = new HandleRef(window, window.Handle);
Reason = reason;
}
/// <summary>The reason the application must block system shutdown. Because users are typically in a hurry when shutting down the system, they may spend only a few seconds looking at the shutdown reasons that are displayed by the system. Therefore, it is important that your reason strings are short and clear.</summary>
/// <value>The reason string.</value>
public string Reason
{
get
{
if (!ShutdownBlockReasonQuery(href.Handle, out var reason))
Win32Error.ThrowLastError();
return reason;
}
set
{
if (value == null) value = string.Empty;
if (ShutdownBlockReasonQuery(href.Handle, out var _))
ShutdownBlockReasonDestroy(href.Handle);
if (!ShutdownBlockReasonCreate(href.Handle, value))
Win32Error.ThrowLastError();
}
}
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
ShutdownBlockReasonDestroy(href.Handle);
}
}
}
...
[DllImport(Lib.User32, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShutdownBlockReasonCreate(HWND hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason);
[DllImport(Lib.User32, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShutdownBlockReasonQuery(HWND hWnd, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszBuff, ref uint pcchBuff);
[DllImport(Lib.User32, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShutdownBlockReasonDestroy(HWND hWnd);
像这样的用法:
using (new PreventShutdownContext(this, "This app is super busy right now."))
{
// Do something that can't be interrupted...
}
我按原样尝试了代码,其中包含 P/Invoke 定义,并对我使用 IntPtr 结构的代码进行了一些修改 window 句柄而不是自定义 HWND 结构,并将我在上面评论中指定的应用程序的主 window 句柄传递给它。
这是设计使然。
documentation(以及您引用的主题)可能有点误导。
Indicates that the system cannot be shut down and sets a reason string to be displayed to the user if system shutdown is initiated.
If an application must block a potential system shutdown, it can call the
ShutdownBlockReasonCreate
function.
这个函数实际上只为您的应用程序设置消息字符串。此功能不会防止您的应用程序被关闭。
要实施关闭块,只需按照您引用的文章中描述的步骤操作即可。您需要对 WM_QUERYENDSESSION
消息和 return FALSE
(0) 做出反应。
作为参考,另请参阅 WM_QUERYENDSESSION
文档。
您可能还会发现有趣的东西 this topic - 它描述了 Windows Vista 引入的变化,并包含如何实现关闭逻辑的最佳实践。
顺便说一句,关于您的应用程序不会有特殊的“对话window”。将显示标准 Windows 关闭 UI(它因 OS 版本而异)。您的应用程序将出现在 “防止关机的应用程序” 列表中,其中包含您使用 ShutdownBlockReasonCreate
功能注册的消息 - 但前提是 returns FALSE
用于 WM_QUERYENDSESSION
消息。
更新
如果上述解决方案(WM_QUERYENDSESSION
)未能解决问题,可能是系统设置忽略了此机制所致。
@ElektroStudios 在他们的研究中发现:
- 如果用户设置了
AutoEndTasks
注册表值(在HKCU\Control Panel\Desktop
注册表项中找到),则关机不会显示任何 UI 让用户取消关机。所以在这些情况下创建“取消关闭原因”是没有用的,因为应用程序无论如何都会被强制立即关闭(以继续关闭)。作为参考,阅读 this MS Docs topic. - 为了使这个东西按预期工作,
AutoEndTasks
注册表值必须为 0(零);否则,任何试图阻止关机的应用程序都将被终止,并且不会在关机时显示 UI。 - 可以将
AutoEndTasks
值添加到覆盖HKCU
配置单元和HKU\{SID}
中定义的值的HKEY_USERS\.DEFAULT\Control Panel\Desktop
键。这意味着,如果AutoEndTasks
在HKCU
中是false
(0) 但在HKU\.DEFAULT
中是true
(1),则应用程序不会阻止系统shutting down and no shutdown UI 将显示。如果AutoEndTasks
在HKU\.DEFAULT
中为false
但在HKCU
中为真,则应用程序将阻止系统关闭并显示关闭 UI。 - 另外一个好处是
AutoEndTasks
值不需要 restart/log-off 系统生效。因此,一旦在适当的键中设置为false
(例如HKEY_USERS\.DEFAULT\Control Panel\Desktop
),应用程序将阻止系统关闭,我们可以在使用完后将此值恢复到之前的状态功能。
如果有人感兴趣,我将在 VB.NET 中分享我的自定义实现。目前这段代码缺少 Windows API 定义和我用来 read/write 到 AutoEndTasks 注册表值的方法(成员name TweakingUtil.AutoEndTasks
在下面的代码中),但你可以得到我认为最重要的想法...
Imports Microsoft.Win32
Imports System.Security
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Provides a mechanism to prevent any system shutdown/restart/log-off request during the life-cycle of a instance of this class.
''' <para></para>
''' Applications should use this class as they begin an operation that cannot be interrupted, such as burning a CD or DVD.
''' <para></para>
''' This class is to be used in either a <see langword="Using"/> statement or for the life-cycle of the current application.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <remarks>
''' Original source-code: <see href="https://github.com/dahall/Vanara/blob/master/WIndows.Forms/Contexts/PreventShutdownContext.cs"/>
''' </remarks>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code lang="vb">
''' Using psc As New PreventShutdownContext("Critical operation is in progress...")
''' ' Do something that can't be interrupted...
''' End Using
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code lang="vb">
''' Public NotInheritable Class Form1 : Inherits Form
'''
''' Private psc As PreventShutdownContext
'''
''' Private Sub AllowShutdown()
''' If (Me.psc IsNot Nothing) Then
''' Me.psc.Dispose()
''' Me.psc = Nothing
''' End If
''' End Sub
'''
''' Private Sub DisallowShutdown()
''' If (Me.psc Is Nothing) Then
''' Me.psc = New PreventShutdownContext("Application defined reason goes here.")
''' End If
''' End Sub
'''
''' Protected Overrides Sub OnShown(ByVal e As EventArgs)
''' Me.DisallowShutdown()
''' MyBase.OnShown(e)
''' End Sub
'''
''' End Class
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example using the <see langword="using"/> statement.
''' <code lang="cs">
''' using (new PreventShutdownContext("Critical operation is in progress...")) {
''' // Do something that can't be interrupted...
''' }
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <code lang="cs">
''' public partial class Form1 : Form {
'''
''' private PreventShutdownContext disallowShutdown;
'''
''' private void AllowShutdown() {
''' if (this.psc != null) {
''' this.psc.Dispose();
''' this.psc = null;
''' }
''' }
'''
''' private void DisallowShutdown() {
''' if (this.psc == null) {
''' this.psc = new PreventShutdownContext("Application defined reason goes here.");
''' }
''' }
'''
''' protected override void OnShown(EventArgs e) {
''' this.DisallowShutdown();
''' base.OnShown(e);
''' }
'''
''' }
''' </code>
''' ----------------------------------------------------------------------------------------------------
Public NotInheritable Class PreventShutdownContext : Implements IDisposable
#Region " Private Fields "
''' <summary>
''' Holds the main window handle for the current application.
''' </summary>
Private ReadOnly hRef As HandleRef
''' <summary>
''' Flag to determine whether the shutdown reason is created.
''' </summary>
Private isReasonCreated As Boolean
''' <summary>
''' Holds the previous value of "HKEY_USERS\.DEFAULT\Control Panel\Desktop" "AutoEndTasks" registry value.
''' <para></para>
''' This registry value is restored when calling <see cref="PreventShutdownContext.Dispose()"/>
''' </summary>
Private ReadOnly previousAutoEndTasksValue As Boolean
#End Region
#Region " Constructors "
''' <summary>
''' Initializes a new instance of the <see cref="PreventShutdownContext"/> class.
''' </summary>
''' <param name="reason">
''' The reason for which the current application must prevent system shutdown.
''' <para></para>
''' Because users are typically in a hurry when shutting down the system,
''' they may spend only a few seconds looking at the shutdown reasons that are displayed by the system.
''' Therefore, it is important that your reason strings are short and clear.
''' </param>
'''
''' <param name="throwOnError">
''' If <see langword="True"/>, an exception will be thrown if
''' the application does not meet the requirements to prevent a system shutdown.
''' <para></para>
''' Default value is <see langword="True"/>.
''' </param>
''' <exception cref="InvalidOperationException">
''' Applications without a user interface can't prevent a system shutdown.
''' </exception>
'''
''' <exception cref="InvalidOperationException">
''' The main window of the current application is not yet created or is not visible.
''' </exception>
'''
''' <exception cref="InvalidOperationException">
''' Only the thread that created the main window of the current application can call this to prevent a system shutdown.
''' </exception>
'''
''' <exception cref="SecurityException">
''' The user does not have the permissions required to create or modify 'AutoEndTasks' registry value.
''' Therefore, the application can't prevent a system shutdown.
''' </exception>
<DebuggerStepThrough>
Public Sub New(ByVal reason As String, Optional ByVal throwOnError As Boolean = True)
If Not Environment.UserInteractive Then
If (throwOnError) Then
Throw New InvalidOperationException(
"Applications without a user interface can't prevent a system shutdown.")
End If
End If
Dim pr As Process = Process.GetCurrentProcess()
Me.hRef = New HandleRef(pr, pr.MainWindowHandle)
If (Me.hRef.Handle = IntPtr.Zero) AndAlso (throwOnError) Then
Throw New InvalidOperationException(
"The main window of the current application is not yet created or is not visible.")
End If
Dim currentThreadId As UInteger = NativeMethods.GetCurrentThreadId()
Dim mainThreadId As Integer = NativeMethods.GetWindowThreadProcessId(Me.hRef.Handle, Nothing)
If (currentThreadId <> mainThreadId) AndAlso (throwOnError) Then
Throw New InvalidOperationException(
"Only the thread that created the main window of the current application can call this to prevent a system shutdown.")
End If
Me.previousAutoEndTasksValue = TweakingUtil.AutoEndTasks
If (Me.previousAutoEndTasksValue) Then
Try
TweakingUtil.AutoEndTasks = False
Catch ex As SecurityException
If (throwOnError) Then
Throw New SecurityException(
"The user does not have the permissions required to create or modify 'AutoEndTasks' registry value. " &
"Therefore, the application can't prevent a system shutdown.", ex)
End If
Catch ex As Exception
If (throwOnError) Then
Throw
End If
End Try
End If
AddHandler SystemEvents.SessionEnding, AddressOf Me.SessionEnding
Me.Reason = reason
End Sub
#End Region
#Region " Properties "
''' <summary>
''' Gets or sets the reason for which the current application must prevent system shutdown.
''' <para></para>
''' Because users are typically in a hurry when shutting down the system,
''' they may spend only a few seconds looking at the shutdown reasons that are displayed by the system.
''' Therefore, it is important that your reason strings are short and clear.
''' </summary>
''' <value>
''' The reason for which the current application must prevent system shutdown.
''' </value>
Public Property Reason As String
Get
Return Me.reason_
End Get
<DebuggerStepThrough>
Set(ByVal value As String)
If value.Equals(Me.reason_, StringComparison.Ordinal) Then
Exit Property
End If
Me.SetReason(value)
Me.reason_ = value
End Set
End Property
''' <summary>
''' ( backing field of <see cref="PreventShutdownContext.Reason"/> property )
''' <para></para>
''' The reason for which the application must prevent system shutdown.
''' </summary>
Private reason_ As String
#End Region
#Region " Event-Handlers "
''' <summary>
''' Handles the <see cref="Microsoft.Win32.SystemEvents.SessionEnding"/> event.
''' </summary>
''' <param name="sender">
''' The source of the event.
''' </param>
'''
''' <param name="e">
''' The <see cref="SessionEndingEventArgs"/> instance containing the event data.
''' </param>
Private Sub SessionEnding(ByVal sender As Object, e As SessionEndingEventArgs)
' By setting "e.Cancel" property to True,
' the application will respond 0 (zero) to "WM_QUERYENDSESSION" message in order to prevent a system shutdown.
'
' For more info:
' https://docs.microsoft.com/en-us/windows/desktop/shutdown/wm-queryendsession
' https://docs.microsoft.com/en-us/windows/desktop/Shutdown/shutdown-changes-for-windows-vista
e.Cancel = True
End Sub
#End Region
#Region " Private Methods "
''' <summary>
''' Sets the reason for which the current application must prevent system shutdown.
''' </summary>
''' <param name="reason">
''' The reason for which the current application must prevent system shutdown.
''' <para></para>
''' Because users are typically in a hurry when shutting down the system,
''' they may spend only a few seconds looking at the shutdown reasons that are displayed by the system.
''' Therefore, it is important that your reason strings are short and clear.
''' </param>
''' <exception cref="Win32Exception">
''' </exception>
<DebuggerStepThrough>
Private Sub SetReason(ByVal reason As String)
Dim succeed As Boolean
Dim win32Err As Integer
If (Me.isReasonCreated) Then
succeed = NativeMethods.ShutdownBlockReasonDestroy(Me.hRef.Handle)
win32Err = Marshal.GetLastWin32Error()
If Not succeed Then
Throw New Win32Exception(win32Err)
End If
End If
succeed = NativeMethods.ShutdownBlockReasonCreate(Me.hRef.Handle, reason)
win32Err = Marshal.GetLastWin32Error()
If Not succeed Then
Throw New Win32Exception(win32Err)
End If
Me.isReasonCreated = True
End Sub
#End Region
#Region " IDisposable Implementation "
''' <summary>
''' Flag to detect redundant calls when disposing.
''' </summary>
Private isDisposed As Boolean
''' <summary>
''' Releases all the resources used by this instance.
''' </summary>
<DebuggerStepThrough>
Public Sub Dispose() Implements IDisposable.Dispose
Me.Dispose(isDisposing:=True)
End Sub
''' <summary>
''' Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
''' Releases unmanaged and, optionally, managed resources.
''' </summary>
''' <param name="isDisposing">
''' <see langword="True"/> to release both managed and unmanaged resources;
''' <see langword="False"/> to release only unmanaged resources.
''' </param>
<DebuggerStepThrough>
Private Sub Dispose(ByVal isDisposing As Boolean)
If (Not Me.isDisposed) AndAlso (isDisposing) Then
RemoveHandler SystemEvents.SessionEnding, AddressOf Me.SessionEnding
NativeMethods.ShutdownBlockReasonDestroy(Me.hRef.Handle)
Me.isReasonCreated = False
Try
TweakingUtil.AutoEndTasks = Me.previousAutoEndTasksValue
Catch
End Try
End If
Me.isDisposed = True
End Sub
#End Region
End Class