1

比如说,如果我打开记事本,在其中输入一些内容并且不保存它,然后从同一个用户会话中调用以下 API:

ExitWindowsEx(EWX_LOGOFF, SHTDN_REASON_MAJOR_OTHER | SHTDN_REASON_MINOR_OTHER | SHTDN_REASON_FLAG_PLANNED);

该用户会话将进入“关闭状态”,其中操作系统将显示一个覆盖窗口,其中显示记事本阻止系统注销用户的消息。在用户单击“取消”或“强制退出”按钮之前,此叠加层不会消失。

所以两部分问题:

  1. 有什么方法可以知道哪些进程阻止了注销/关闭进程?

  2. 有没有办法以编程方式取消这个用户会话“关闭状态”?

PS。可以通过调用 GetSystemMetrics( SM_SHUTTINGDOWN ) 来检测此状态;

编辑:与下面的答案相反,我并没有试图阻止系统关闭,也没有任何用户模式进程被“挂起”。

EDIT2:这是我试图取消/关闭的覆盖的屏幕截图: 在此处输入图像描述

4

2 回答 2

4

问题 2:“有没有办法以编程方式取消此关闭状态?”

简而言之,答案不是真的。而且您也不应该真正以编程方式停止关机,除非:关机会导致严重的数据丢失或严重影响后续系统启动时的用户体验。但仅举一个例子:想象一台计算机过热 - 以编程方式停止关机可能会导致系统崩溃(以及非常愤怒的用户)。

系统关闭也不是您需要监控的唯一事情。还有休眠和挂起事件(查看 WM_POWERBROADCAST 消息)。

也就是说,Windows 提供了大量的机制来检测系统关闭。例如:

如果您的应用程序有消息泵,您可以选择在 Windows 轮询正在运行的应用程序对 WM_QUERYENDSESSION进行投票时返回 FALSE ,但是从 Vista 开始的 Windows 在超时后仍将强制关闭。从 Vista 开始,您可以(并且需要)在向 WM_QUERYENDSESSION 返回 false 后关闭 ShutdownBlockReasonCreate 。

如果您的应用程序作为服务运行,您可以使用RegisterServiceCtrHandlerEx然后SetServiceStatus通过设置 SERVICE_ACCEPT_PRESHUTDOWN 来获得 3 分钟的关闭扩展宽限期,这将为您提供 SERVICE_CONTROL_PRESHUTDOWN 通知。当然,您不会收到注销通知,因为服务不受注销的影响。Pre-Vista 您可以注册 SERVICE_CONTROL_SHUTDOWN 通知。

控制台应用程序(以及 gui 应用程序,但它没有意义)可以使用SetConsoleCtrlHandler来通知 CTRL_LOGOFF 和 CTRL_SHUTDOWN_EVENT。

在低得多的级别上,可以尝试挂钩 API 函数,例如 NTShutdown 甚至 NtSetSystemPowerState,这显然是“在任何类型的重新启动期间调用的最后一件事”。但我强烈建议不要尝试这个。

也就是说,有一些方法可以真正强烈地坚持不应该关闭系统。考虑以下:

1.) 尝试将您的应用程序注册为第一个接收关机通知。就像是:

    // http://msdn.microsoft.com/en-us/library/windows/desktop/ms686227(v=vs.85).aspx
    if(!SetProcessShutdownParameters(0x4ff, 0)) //  greedy highest documented System reserved FirstShutdown
    {
        // Fallback
        if(!SetProcessShutdownParameters(0x3ff, 0)) // highest notification range for applications
        {
             // shouldn't happen
        }
    }

2.) 在 WM_QUERYENDSESSION 上返回 FALSE 从 Vista 开始,在 WM_QUERYENDSESSION 上返回 false 后调用 ShutdownBlockReasonCreate()。

3.) 告诉 Windows 您需要系统保持正常运行并可用。看看 http://msdn.microsoft.com/en-us/library/windows/desktop/aa373208(v=vs.85).aspx

SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);

4.) 清理,从 Vista 开始调用 ShutdownBlockReasonDestroy(),然后彻底关闭系统。

您还可以尝试未记录的功能(至少它不再在 MSDN 上)“user32.dll”中的 CancelShutdown,它在某些时候(仍然可能)曾经非常像使用中止标志调用 shutdown.exe。

你的旅费可能会改变。

于 2012-11-10T11:42:55.107 回答
0

按照您的编辑,这使您的问题更清楚:

如果您监视 WM_QUERYENDSESSION 并对其做出 FALSE 响应,您可以在预定的时间段内从仍在运行的进程中轮询,然后发出带有标志EWX_FORCEIFHUNG的 ExitWindowsEx 调用,例如在 WM_ENDSESSION 上。或者您实际上可以在收到 WM_QUERYENDSESSION 时先发制人地调用它 - 问题是:如果强制关闭会导致严重的数据丢失怎么办?在这一点上,您对系统用户没有任何好处。

根据您的评论更新:

这个怎么样:

要找出阻塞应用程序:

  1. 使用 SetProcessShutdownParameters 注册您的应用程序,使其排在第一位以获取 WM_QUERYENDSESSION。
  2. 对 WM_QUERYENDSESSION 响应 FALSE,然后在 Vista 上调用 ShutdownBlockReasonCreate 为自己争取时间。
  3. 获取链中的第一个窗口 HWND top = GetTopWindow(NULL)
  4. 从 hwnd GetWindowThreadProcessId() 获取进程 ThreadId(将其与您正在运行的应用程序进行比较;))
  5. 使用 SendMessageTimeOut 向 hWnd 发送消息 - 如果您没有收到响应(超时),您可能已经找到了阻塞窗口。转到第 6 步。如果没有,请跳到第 7 步。
  6. 使用 OpenProcess() 和 GetWindowThreadProcessId 返回的句柄来获取进程句柄,并调用 GetModuleBaseName() 来获取挂起进程的名称。
  7. 如果您没有找到挂起的窗口,请使用 GetNextWindow() 枚举下一个窗口并返回步骤 4。

您还可以尝试使用上面的枚举窗口句柄技术来确定您是否可以获得“挂窗”叠加层的句柄。这样做可能会让你有机会做一个发送键来取消状态。我敢打赌,您将无法访问它,但我还没有尝试过 :)

同样,您的里程可能会有所不同:)

于 2012-11-12T23:32:12.137 回答