代码之家  ›  专栏  ›  技术社区  ›  ElektroStudios

调用“ShutdownBlockReasonCreate”函数不会阻止用户关闭系统

  •  1
  • ElektroStudios  · 技术社区  · 6 年前

    This 文章说:

    如果应用程序必须阻止潜在的系统关闭,它可以调用 将显示给用户的字符串。

    而在 ShutdownBlockReasonCreate

    指示无法关闭系统并设置原因字符串

    对话框窗口的出现在 this 讨论:

    如果用户在某个时间段内未采取任何操作,“无论如何都要关机” 秒。

    关闭BlockReasonCreate 传递当前应用程序的主窗口句柄,确保函数成功,并通过调用 ShutdownBlockReasonQuery

    为什么对我的系统没有影响?,如何解决这个问题?。

    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公司 HWND 结构,并将应用程序的主窗口句柄传递给它,如我在上面的注释中指定的。

    0 回复  |  直到 6 年前
        1
  •  6
  •   Community CDub    5 年前

    这是故意的。

    documentation (以及您引用的主题)可能会有点误导。

    指示无法关闭系统,并设置在启动系统关闭时向用户显示的原因字符串。

    如果应用程序必须阻止潜在的系统关闭,它可以调用 ShutdownBlockReasonCreate 功能。

    你的申请。此函数不起作用 防止 你的申请不会被关闭。

    WM_QUERYENDSESSION 消息和返回 FALSE (0). 有关参考,请参见 WM_QUERYENDSESSION

    你可能也会觉得有趣 this topic -它描述了WindowsVista引入的更改,并包含了如何实现关闭逻辑的最佳实践。

    顺便说一句,关于你的应用程序没有特别的“对话框窗口”。将显示标准的Windows关机UI(根据操作系统版本的不同而有所不同)。您的应用程序将出现在 “防止关机的应用程序” 列出使用 关闭BlockReasonCreate 函数-但仅当它返回 对于 信息。


    更新

    如果上述解决方案( )无法解决问题,可能是系统设置忽略了此机制造成的。

    正如@ElektroStudios在研究中发现的:

    • AutoEndTasks 注册表值集(位于 HKCU\Control Panel\Desktop this MS Docs topic
    • 为了使这件事按预期进行 字符串值 注册表值必须为0(零);否则,任何试图阻止关机的应用程序都将被终止,并且在关机时不会显示UI。
    • 字符串值 HKEY_USERS\.DEFAULT\Control Panel\Desktop 重写中定义的值的键 HKCU HKU\{SID} 字符串值 false (0)英寸 true HKU\.DEFAULT ,则应用程序将不会阻止系统关闭,并且不会显示关闭UI。如果 在里面 HKU\.默认 香港大学 ,则应用程序将阻止系统关闭,并显示关闭UI。
    • 还有一个很好的观点是 字符串值 HKEY\ U用户\默认\控制面板\桌面 ),应用程序将阻止系统关闭,我们可以在完成使用该功能后将此值还原为其以前的状态。
        2
  •  1
  •   ElektroStudios    6 年前

    如果有人感兴趣,我将在中分享我的自定义实现VB.NET版. 当前,此代码缺少Windows API定义和我用于读取/写入的方法 字符串值 注册表值(名为 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