Вызов функции «ShutdownBlockReasonCreate» не мешает пользователю завершить работу системы. - PullRequest
0 голосов
/ 23 февраля 2019

Эта статья гласит:

Если приложение должно блокировать возможное отключение системы, оно может вызвать функцию ShutdownBlockReasonCreate.Вызывающая сторона предоставляет строку причины, которая будет отображаться пользователю.

И в документации ShutdownBlockReasonCreate это четко указывает, что диалоговое окно со строкой причины будет отображаться дляпользователь при попытке выключения:

Указывает, что система не может быть выключена, и задает строку причины, которая будет отображаться пользователю, если выключение системы инициируется

Ипоявление этого диалогового окна подтверждается в этом обсуждении:

Пользователь может нажать «Все равно выключить».Кроме того, система предполагает «все равно отключиться», если пользователь не предпринимает никаких действий в течение некоторого количества секунд.

Однако после того, как я позвоню ShutdownBlockReasonCreate , передавая дескриптор главного окнатекущее приложение, гарантирующее, что функция выполнена успешно, и двойное заверение, вызывая функцию ShutdownBlockReasonQuery для получения строки причины, это не мешает пользователю завершить работу системы, и диалоговое окно не отображается.

Почему это не влияет на мою систему? И как мне решить эту проблему?.

Я работаю в Windows 10 x64 с учетной записью администратора (встроенной) икод, который я использую, взят из этого 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 структуру для дескрипторов окон вместо этого пользовательского * 1046Структура * HWND и передача ей дескриптора главного окна приложения, как я указал в комментарии выше.

Ответы [ 2 ]

0 голосов
/ 27 февраля 2019

Если кому-то интересно, я делюсь своей собственной реализацией в VB.NET.В настоящее время в этом коде отсутствуют определения Windows API и метод, который я использую для чтения / записи в значение реестра AutoEndTasks (член с именем 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
0 голосов
/ 26 февраля 2019

Это специально.

Документация (а также тема, на которую вы ссылались) может слегка вводить в заблуждение.

Указывает, что система не можетбыть выключен и устанавливает строку причины, которая будет отображаться пользователю, если инициировано отключение системы.

Если приложение должно блокировать возможное отключение системы, оно может вызвать функцию ShutdownBlockReasonCreate.

Эта функция фактически устанавливает только строку сообщения для вашего приложения.Эта функция не предотвращает закрытие вашего приложения.

Чтобы реализовать блок отключения, просто выполните шаги, описанные в статье, на которую вы ссылались.Вам нужно отреагировать на сообщение WM_QUERYENDSESSION и вернуть FALSE (0).Для справки см. Также документацию WM_QUERYENDSESSION.

Вы также можете найти интересную эту тему - она ​​описывает изменения, внесенные в Windows Vista, и содержит лучшие рекомендациикак реализовать логику выключения.

Кстати, в вашем приложении не будет специального «диалогового окна».Будет показан стандартный пользовательский интерфейс завершения работы Windows (он зависит от версии ОС).Ваше приложение появится в списке «Приложения, предотвращающие отключение» вместе с сообщением, которое вы зарегистрируете с помощью функции ShutdownBlockReasonCreate - но только если оно вернет FALSE для сообщения WM_QUERYENDSESSION.


Обновление

Если вышеуказанное решение (WM_QUERYENDSESSION) не решает проблему, это может быть вызвано настройкой системы, которая просто игнорирует этот механизм.

Как@ElektroStudios обнаружил в своем исследовании:

  • Если у пользователя установлено значение реестра AutoEndTasks (находится в разделе реестра HKCU\Control Panel\Desktop), то при завершении работы не отображается пользовательский интерфейс, позволяющий пользователю отменить действие.неисправность.Поэтому в этих обстоятельствах бесполезно создавать «причину отмены выключения», потому что приложение будет вынуждено немедленно закрыться в любом случае (для продолжения выключения).Для справки прочитайте эту тему MS Docs .
  • Для того, чтобы эта штука работала должным образом, значение реестра AutoEndTasks должно быть 0 (ноль);в противном случае любое приложение, которое пытается предотвратить завершение работы, будет закрыто, и пользовательский интерфейс не будет отображаться при завершении работы.
  • Значение AutoEndTasks можно добавить к клавише HKEY_USERS\.DEFAULT\Control Panel\Desktop, которая переопределяет значение, определенное в кустах HKCU и HKU\{SID}.Это означает, что если AutoEndTasks равно false (0) в HKCU, но равно true (1) в HKU\.DEFAULT, то приложение не будет препятствовать завершению работы системы, и пользовательский интерфейс выключения не будет отображаться.Если AutoEndTasks равно false в HKU\.DEFAULT, но истинно в HKCU, то приложение будет препятствовать завершению работы системы, и будет отображаться пользовательский интерфейс завершения работы.
  • Также это хорошоявляется то, что значение AutoEndTasks не требует перезапуска / выхода из системы для вступления в силу.Таким образом, как только он установлен на false в соответствующей клавише (например, HKEY_USERS\.DEFAULT\Control Panel\Desktop), приложение предотвратит отключение системы, и мы сможем вернуть это значение в его предыдущее состояние, когда закончим использовать функциональность.
...