ApriorIT
使用WinAPI处理操作系统关闭事件

当用户关闭计算机或结束会话时,总有一种风险,即活跃的应用程序不会自动保存最新的更改或将无法正常结束操作。这可能会导致用户的数据和你花费额外资源的损失,恢复它。

为了避免这种情况,您可以教会应用程序或服务检测操作系统(OS)关闭和用户注销事件。这将允许您的应用程序正确停止工作,或要求用户延迟关机以避免数据丢失。在本文中,我们将向您展示如何在的帮助下处理控制台应用程序、图形用户界面(GUI)应用程序和服务的操作系统关闭事件Windows API

这篇文章对于在Windows软件上工作的开发团队很有用,他们正在寻找在操作系统关闭之前保存或记录数据的方法。

内容:

操作系统关机检测:为什么以及如何实现

控制台应用程序

GUI应用程序

Windows服务

结论

操作系统关机检测:为什么以及如何实现

应用程序需要检测OS停机来完成短,但很重要的措施:

  • 保存所有未保存的数据
  • 通知网络组件计算机将被关闭
  • 记录关机,以便以后进行分析
为什么要检测关机事件

检测系统关闭的高级过程对于所有类型的Windows应用程序和服务都是相同的。为了检测关闭,我们创建一个回调函数并在系统中注册它。当某个Windows事件发生时,系统调用回调函数,通过输入参数传递有关事件的信息。然后回调函数分析输入参数数据,决定发生了哪个事件,并相应地执行代码。

没有一种方法可以让所有类型的应用程序检测到操作系统关闭和用户注销。原因是不同类型的应用程序在语法和功能上有所不同。这就是为什么在本文中,我们将向您展示三个单独的教程——控制台应用程序、GUI应用程序和Windows服务。请注意,我们描述的方法将仅帮助您检测关机和注销事件。要检测操作系统睡眠,您需要实现其他机制。

检测三种软件关闭事件的解决方案

控制台应用程序

假设我们有一个控制台应用程序,它具有默认处理程序,不会检测关闭和注销事件。为了让应用程序这样做,我们将创建自己的处理程序函数:

       
BOOL WINAPI HandlerRoutine(DWORD dwCtrlType中的);

接下来,我们需要使用以下API函数将此函数注册为控制台应用程序的处理程序:

       
BOOL WINAPI SetConsoleCtrlHandler(_In_opt_PHANDLER_ROUTINE handler,_In_BOOL Add);

下面是它在应用程序代码中的样子:

       
BOOL WINAPI HandlerRoutine(DWORD dwCtrlType){switch(dwCtrlType){case CTRL\u SHUTDOWN\u EVENT://计算机正在关闭返回TRUE;case CTRL\u LOGOFF\u EVENT://当前用户注销返回TRUE;default://我们不关心此事件//使用默认处理程序}返回FALSE;}int\u tmain int(int argc,_TCHAR*argv[]){SetConsoleCtrlHandler(HandlerRoutine,TRUE);//将HandlerRoutine添加到处理程序列表getchar();//模拟工作返回0;}

如果计算机关闭或用户注销,则HandlerRoutine函数应该返回TRUE。如果此函数返回错误的,操作系统将使用控制台应用程序列表中的下一个处理程序。Windows将重复此过程,直到处理程序返回真的

注意,系统启动HandlerRoutine在一个单独的线程。因此,您可能需要采取额外的措施来跨多个线程同步资源。

相关服务

操作系统管理方案

注:如果控制台应用程序加载gdi32.dll或user32.dll库或调用这些dll中使用的函数,而不直接使用它们,Windows将其视为GUI应用程序。在这种情况下,Microsoft开发人员网络(MSDN)建议通过Windows消息使用检测器。例如,您可以创建一个大小为零的假窗口。在下一节中,我们将向您展示如何实现这样的检测器。

MSDN还警告,在处理CTRL_CLOSE_EVENT,CTRL\u注销\u事件, 和CTRL_SHUTDOWN_EVENT调用它们的信号、控制台函数和C运行时函数可能无法正常工作。当操作系统在执行进程信号处理程序之前调用内部控制台清理例程时,就会发生这种情况。

为了研究这个话题更深,随意学习的MSDN的描述SetConsoletrlHandler函数和控制台应用程序的WinAPI事件处理。在大多数情况下,在实现之后HandlerRoutine,您的控制台应用程序将能够检测到操作系统关闭。

另请阅读:
如何使用WinAPI拍摄多监视器屏幕截图

GUI应用程序

对于GUI应用程序,只有关闭和重启才被认为是暂停操作。进入睡眠模式并不是一个非常重要的事件,因为它不会改变GUI应用程序的任何工作。在某些情况下,我们可能需要检测休眠模式,以便向其他系统组件发送PC将要休眠的消息。然而,大多数GUI应用只需要检测操作系统关闭和重启的机制。让我们看看这些机制是如何工作的。

GUI应用程序通过窗口消息接收目标事件的信息。这就是为什么我们需要WM_查询会话WM_POWERBROADCAST使GUI应用程序能够检测操作系统关闭的消息。

Windows发送WM_查询会话当用户发起用户会话关闭过程时,窗口消息。关闭计算机并重新启动它也会导致用户会话结束。因此,关于这些事件的消息通过相同的窗口消息传递。

我们使用WM_POWERBROADCAST获取有关在系统中挂起操作的信息的消息。以下是我们如何在GUI应用程序中处理此消息:

       
// ...情况下WM_POWERBROADCAST:{如果(wParam中== PBT_APMSUSPEND)//计算机将暂停休息;}情况下WM_QUERYENDSESSION:{如果(lParam的== 0)//计算机正在关闭,如果((lParam的&ENDSESSION_LOGOFF)== ENDSESSION_LOGOFF)//用户登录关中断;} // ...

这个wParam中lParam中的参数WM_POWERBROADCAST包含各种系统事件的标识符,包括关机。对于WM_查询会话窗口消息IParam值0表示重新启动或关闭,而其他值表示其他事件。

请注意,我们分别处理关闭和注销事件,因为它们不一定是连接的。

另请阅读:
如何使用Pywinauto自动化Windows应用程序的GUI测试:专家建议

收到后我们能做什么WM_查询会话?

如果我们什么也不做,Windows会显示一条警告消息这些应用程序正在阻止关机用户可以取消关机或强制继续关机,而不考虑等待的应用程序。在这种情况下,我们的应用程序可以采用以下两种方式之一:

  • 关闭它,让系统立即关闭
  • 显示一条警告消息,向用户解释不应立即重新启动的原因

最后一个选项适用于Windows Vista和更高版本的Windows。下面是它的外观:

       
案例WM_QUERYENDSESSION:{如果(lParam==0){//计算机正在关闭ShutdownBlockReasonCreate(hwnd,_T(“请不要杀我”);}中断;}

对于用户来说,消息看起来像这样:

给用户的警告消息

给用户的警告消息

如果我们不想在应用该块关闭(例如,如果我们需要从用户隐藏),我们可以实现的列表中显示我们的应用程序SetProcessShutdownParameters函数。在此函数中,要特别注意dwLevel范围。如果我们将其值改为SHUTDOWN_NORETRY,我们的GUI应用程序不会阻止关闭:

       
DWORD dwLevel = 0;DWORD dwFlags中= 0;如果(GetProcessShutdownParameters(&dwLevel,&dwFlags中))SetProcessShutdownParameters(dwLevel,SHUTDOWN_NORETRY);

如果调用默认处理程序以外的其他程序,则此代码将不起作用WM_查询会话, 然而。这就是为什么我们需要修改我们的窗口消息处理程序以下列方式:

       
case WM_QUERYENDSESSION: {if (lParam == 0){//计算机正在关闭}if ((lParam & ENDSESSION_LOGOFF) == ENDSESSION_LOGOFF){//用户正在注销}返回DefWindowProc(hwnd, msg, wParam, lParam);}

现在,让我们来看看WM_POWERBROADCAST. 此消息有助于我们通过以下方式检测挂起的操作系统操作事件:wParam==PBT_APMSUSPEND. 另外,从WM_POWERBROADCAST窗口消息,我们可以获得恢复事件和电源设置更改的不同变体(例如,当用户更改电源计划时)。

这就是你如何使用WM_查询会话WM_POWERBROADCAST为检测在GUI应用程序OS关闭。现在,让我们来看看在相同的工艺Windows服务。

另请阅读:
使用Python连接Windows API的全面指南

Windows服务

检测必要的事件为Windows服务的机制是相同的控制台应用程序的方法,但是用稍微不同的语法和许多更多的功能。让我们用一起来看看这个机制通用的Windows服务

与控制台应用程序一样,我们需要创建回调函数并将其注册为服务控制请求处理程序。

我们可以使用两个函数:

在我们的示例中,我们将使用第二个函数,因为它允许我们处理比第一个更多的控件。下面是我们如何实现的RegisterServiceCtrlHandlerEx我们的服务:

       
SERVICE_STATUS_HANDLE WINAPI RegisterServiceCtrlHandlerEx(_In_ LPCTSTR lpServiceName, _In_ LPHANDLER_FUNCTION_EX lpHandlerProc, _In_opt_ LPVOID lpContext);

在此代码中,HandlerProc参数语法如下所示:

       
DWORD WINAPI HandlerEx(u在uuORD dwControl中,u在uuORD dwEventType中,u在uvoid lpEventData中,u在void lpContext中);

有关由HandlerProc参数管理的控件的详细信息,请浏览LPHANDLER\u函数\u EX回调函数

我们还需要指定我们要在服务的启动过程中的控制:

       
g_ServiceStatus.dwControlsAccepted=服务接受停止服务接受电源事件服务接受会话更改服务接受关机;g_ServiceStatus.dwCurrentState=服务运行;g_ServiceStatus.dwWin32ExitCode=0;g_ServiceStatus.dwCheckPoint=0;设置服务状态(g_状态句柄和g_服务状态);

如果我们不这样做,我们的处理程序将不会被调用SERVICE_ACCEPT_STOPSERVICE_ACCEPT_POWEREVENT控制。

除了控制代码之外,处理程序还接收到另一个参数:eventType.对于某些控件,这个参数等于0,没有任何意义。但对于服务\控制\电源事件SERVICE_CONTROL_SESSIONCHANGE控制,例如,该eventType参数可用于定义powereventsessionchange发生群体性事件。这个事件数据这些事件的参数指向包含此事件的附加数据的结构:POWERBROADCAST_SETTING结构分powereventWtsession\u通知指出sessionchange

下面是我们的处理程序实现示例:

       
DWORD WINAPI CtrlHandlerEx(DWORD CtrlCode,DWORD EVENTTYPE,LPVOID EVENTDATA,LPVOID上下文){开关(CtrlCode){情况SERVICE_CONTROL_STOP:{//处理服务停止返回NO_ERROR;}情况下SERVICE_CONTROL_POWEREVENT:{如果(EVENTTYPE == PBT_APMQUERYSUSPEND){//计算机被挂起}返回NO_ERROR;}情况下SERVICE_CONTROL_SESSIONCHANGE:{开关(EVENTTYPE){情况WTS_SESSION_LOGOFF://用户登录关中断;案例WTS_SESSION_LOCK://用户锁定会话中断;}返回NO_ERROR;}情况下SERVICE_CONTROL_SHUTDOWN:{//计算机正在关闭返回NO_ERROR;}默认:{//事件,我们不处理}}返回ERROR_CALL_NOT_IMPLEMENTED;}

在主线程,我们通过执行CtrlHandlerEx作用我们的Windows服务处理此问题的时间限制约为20秒SERVICE_CONTROL_SHUTDOWN控制在此之后,无论服务是否已完成关闭准备,系统都将关闭。如果服务已完成准备,它将返回无错误并关闭,告知系统不要等待20秒。

如果该服务进行一些长期和重要的活动是不允许的操作系统迅速关闭的过程中,我们可以用另一种方法来处理停工。而不是这一点SERVICE_CONTROL_SHUTDOWN控件,我们可以订阅SERVICE_CONTROL_PRESHUTDOWN. 请注意,Windows XP和Windows Server 2003不支持此控件。

在其他Windows操作系统中,如果服务收到预关机通知,系统将暂停关机过程,直到预关机时间结束或预关机控制处理程序完成其任务后才恢复关机过程。

中的超时定义服务\u预关机\u信息结构,默认设置为180000毫秒(三分钟)。我们可以设定自己的价值观服务\u预关机\u信息使用ChangeServiceConfig2函数。

另请阅读:
如何加速微服务开发:应用代码生成的实用指南

注:由于服务已收到SERVICE_CONTROL_PRESHUTDOWN,它不再能够接收SERVICE_CONTROL_SHUTDOWN,因此是有意义的只订阅了两个事件之一。

您可以使用SERVICE_CONTROL_SHUTDOWN与使用所有其他结构的方式相同。不要忘记将其添加到已接受的控件列表中:

       
g_ServiceStatus.dwControlsAccepted=服务接受停止服务接受电源事件服务接受会话更改服务接受预关机;

如果我们抓到SERVICE_CONTROL_PRESHUTDOWN,我们可以执行在处理某些行动:

       
案例服务控制预关机:{g_logger.WriteToLog(“检测到预关机”);睡眠(30000)//模拟长时间工作返回无错误;}

在这种情况下,关于系统关闭消息警告将被显示给用户30秒。

由于关机和预关机之间的选择会影响用户体验,所以我们建议仅在有可能丢失数据或在系统重启后需要很长时间才能恢复服务的活动状态和相应数据的情况下才使用它。

在所有其他情况下,最好使用SERVICE_CONTROL_SHUTDOWN以我们上面描述的方式。它可以帮助Windows服务检测关机并在不中断用户会话的情况下完成其工作。

为什么要检测关机事件

结论

对于需要在关闭前保存数据或结束关键进程的Windows应用程序,检测关闭和用户注销的能力可能至关重要。在本文中,我们向您展示了如何在控制台和GUI应用程序以及Windows服务中实现此功能。

需要更多这方面的专业知识吗?我们有在这方面具有专业知识的开发人员OS管理它们可以帮助您改进解决方案。联系我们,让我们开始讨论您的项目!

告诉我们您的项目
给我们发提案请求吧!我们稍后会告诉你细节和估价。

浏览
单击“发送”,表示您同意处理您的数据

预约一次试探性电话

没有任何特定的任务,但我们的技能似乎很有趣?

快速获取Apriorit简介,以更好地了解我们的团队能力。

联系我们

  • +1 202-780-9339
  • [受电子邮件保护]
  • 美国威尔明顿市银边路3524号35B室,邮编:19810-4929
  • D-U-N-S号码:117063762
Baidu