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

Ele*_*ios 3 .net c# pinvoke winapi shutdown

这篇文章说:

如果应用程序必须阻止潜在的系统关闭,它可以调用 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

...

[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);
Run Code Online (Sandbox Code Playgroud)

像这样的用法:

using (new PreventShutdownContext(this, "This app is super busy right now."))
{
  // Do something that can't be interrupted...
}
Run Code Online (Sandbox Code Playgroud)

我按原样尝试了代码,使用它的 P/Invoke 定义,并对我使用IntPtr结构作为窗口句柄而不是自定义HWND结构的代码进行了一些修改,并将主窗口句柄传递给它我在上面的评论中指定的应用程序。

dym*_*oid 6

这是设计使然。

文件(以及你所引用的主题),可有些误导。

指示系统无法关闭,并设置一个原因字符串,如果系统启动,则显示给用户。

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

这个函数实际上只是为您的应用程序设置消息字符串。此功能不会阻止您的应用程序被关闭。

要实现关闭块,只需按照您引用的文章中描述的步骤操作即可。您需要对WM_QUERYENDSESSION消息做出反应并返回FALSE(0)。作为参考,另请参阅WM_QUERYENDSESSION文档。

您可能还会发现这个主题很有趣- 它描述了 Windows Vista 引入的更改,并包含如何实现关闭逻辑的最佳实践。

顺便说一下,关于您的应用程序不会有特殊的“对话窗口”。将显示标准的 Windows 关机 UI(它因操作系统版本而异)。您的应用程序将与您使用该功能注册的消息一起出现在“防止关机的应用程序”列表中ShutdownBlockReasonCreate- 但前提是它返回FALSEWM_QUERYENDSESSION消息。


更新

如果上述解决方案 ( WM_QUERYENDSESSION) 没有解决问题,则可能是由于系统设置忽略了此机制。

正如@ElektroStudios 在他们的研究中发现的那样:

  • 如果用户AutoEndTasks设置了注册表值(在HKCU\Control Panel\Desktop注册表项中找到),则关机不会显示任何 UI 来让用户取消关机。所以在这些情况下创建“取消关闭原因”是没有用的,因为应用程序在任何情况下都会被强制立即关闭(以继续关闭)。作为参考,请阅读此 MS 文档主题
  • 为了使这件事按预期工作,AutoEndTasks注册表值必须为 0(零);否则,任何试图阻止关机的应用程序都将被终止,并且在关机时不会显示任何 UI。
  • AutoEndTasks值可以添加到HKEY_USERS\.DEFAULT\Control Panel\Desktop覆盖HKCUhive 和HKU\{SID}. 这意味着,如果AutoEndTasksfalse(0) inHKCU但是true(1) in HKU\.DEFAULT,则应用程序不会阻止系统关闭并且不会显示关闭 UI。如果AutoEndTasksfalseHKU\.DEFAULT,但是是真实的HKCU,那么应用程序将防止系统关闭和关机界面将显示。
  • 另一个好处是该AutoEndTasks值不需要重新启动/注销系统即可生效。因此,一旦将其设置为false正确的键(例如HKEY_USERS\.DEFAULT\Control Panel\Desktop),应用程序将阻止系统关闭,当我们完成使用该功能时,我们可以将此值恢复到以前的状态。