处理控制台消息
作者:Tocy 时间:2012-05-25
1. 问题和需求描述
原有一个visual studio Win32 Console Application(传说中的控制台程序)开发的程序,由于程序本身有类似死循环的机制,也就是几乎一直运行,除非强制关闭那种。我们都知道如果直接关闭控制台,程序是可以结束的,但是这种结束是以程序异常结束为代价的(如果不相信,你可以写个死循环程序试试,将程序输出重定位到文件中,防止你看不到程序本身的输出,至于验证,我想你自己应该有思路的。)。那么该如何让程序正常结束呢?或者希望在控制台关闭的时候做一些处理,可能是给另外一个程序发送一个消息。那么又该怎么处理或者捕获控制台的退出事件呢?
问题到此问题,需求也基本是希望捕获控制台事件,比如关闭,比如按Ctrl+C退出等等。
(如果你对这个问题感兴趣,可以继续看下去,没有的话,就到此为止吧:-D)。
2. 解决方案
控制台处理函数(Console Control Handlers)
链接:
每个控制台进程都有单独的控制处理函数列表,通常调用这些函数发生在控制台进程接收到ctrl+c、ctrl+break、ctrl+close信号。默认情况下控制处理函数列表中只有一个处理函数,其会调用ExitProcess。一个控制台进程可以使用SetConsoleCtrlHandler函数来添加或者删除额外的HandlerRoutine。这并不会对其他进程的控制台处理函数列表造成影响。系统丢用控制处理函数的顺序是按照最后注册首先调用的原则(栈),直到针对某控制信号的处理函数返回TRUE为止。如果所有处理函数都没有返回TRUE,就调用默认的处理函数(注:处理函数handler)。
(1)HandlerRoutine
声明格式如下:
BOOL WINAPI HandlerRoutine(
__in DWORD dwCtrlType // 控制事件类型
);
该函数只有一个参数,表示控制台发生什么消息。参数的具体值如表1所示:
表格 1 HandlerRoutine参数值及其意义
消息(宏) | 实际值 | 意义 |
CTRL_C_EVENT | 0 | 收到ctrl+c信号,来自键盘输入或者函数产生的模拟信号 |
CTRL_BREAK_EVENT | 1 | 收到ctrl+break信号,来自键盘输入或者函数产生的模拟信号 |
CTRL_CLOSE_EVENT | 2 | 控制台关闭信号,用户点击控制台标题栏上的关闭按钮或者使用任务管理器结束控制台进程。 |
CTRL_LOGOFF_EVENT | 5 | 用户切换或者用户注销的信号。 |
CTRL_SHUTDOWN_EVENT | 6 | 操作系统关闭时发送的信号。 |
通常我们处理的是前三个消息,如果有对后面两个消息感兴趣的情查看msdn,网址如下:
这里说明下返回值,如果该函数处理了某个信号,函数返回值必须为TRUE(也就是说不希望其他函数再处理这种信号),如果返回值为FALSE,那么该进程处理函数列表中的其他函数还会继续处理该信号。
CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT和CTRL_SHUTDOWN_EVENT通常被用来处理一些程序的清理工作,然后调用ExitProcess API。另外,这三个事件有超时机制,CTRL_CLOSE_EVENT是5秒,另外两个是20秒。如果程序超时候,系统将会弹出结束进程的对话框。如果用户选择了结束进程,任何清理工作都不会做,所以应该在超时时间内完成工作。
(2)安装事件钩子,建立回调函数
——SetConsoleCtrlHandler
看这个函数的名字,基本上可以确定这是处理控制台相关函数,看看msdn说明。
原型如下:
BOOL SetConsoleCtrlHandler(
PHANDLER_ROUTINE HandlerRoutine, // 回调函数
BOOL Add // 表示添加还是删除
);
函数用法很明显。如果函数执行成功,返回非零值,失败则返回0。
msdn地址:
下面是微软给的例子:
#include <windows.h>
#include <stdio.h>
BOOL CtrlHandler( DWORD fdwCtrlType )
{
switch( fdwCtrlType )
{
// Handle the CTRL-C signal.
case CTRL_C_EVENT:
printf( "Ctrl-C event\n\n" );
Beep( 750, 300 );
return( TRUE );
// CTRL-CLOSE: confirm that the user wants to exit.
case CTRL_CLOSE_EVENT:
Beep( 600, 200 );
printf( "Ctrl-Close event\n\n" );
return( TRUE );
// Pass other signals to the next handler.
case CTRL_BREAK_EVENT:
Beep( 900, 200 );
printf( "Ctrl-Break event\n\n" );
return FALSE;
case CTRL_LOGOFF_EVENT:
Beep( 1000, 200 );
printf( "Ctrl-Logoff event\n\n" );
return FALSE;
case CTRL_SHUTDOWN_EVENT:
Beep( 750, 500 );
printf( "Ctrl-Shutdown event\n\n" );
return FALSE;
default:
return FALSE;
}
}
int main( void )
{
if( SetConsoleCtrlHandler( (PHANDLER_ROUTINE) CtrlHandler, TRUE ) )
{
printf( "\nThe Control Handler is installed.\n" );
printf( "\n -- Now try pressing Ctrl+C or Ctrl+Break, or" );
printf( "\n try logging off or closing the console...\n" );
printf( "\n(...waiting in a loop for events...)\n\n" );
while( 1 ){ }
}
else
{
printf( "\nERROR: Could not set control handler");
return 1;
}
return 0;
}
个人认为下面代码还有点意思,如何在关闭控制台的时候的时候通知运行程序结束。
//
// setConsole_main.cpp
// 控制台消息处理
//
#include <STDIO.H>
#include <Windows.h>
HANDLE hExit = NULL;
void run()
{
hExit = CreateEvent(NULL,TRUE,FALSE,"Test001");
WaitForSingleObject(hExit,INFINITE);
}
BOOL HandlerRounte(DWORD dwCtrlType)
{
BOOL Result = FALSE;
printf("Start now...\r\n");
switch(dwCtrlType)
{
case CTRL_C_EVENT:// ctrl + c
case CTRL_BREAK_EVENT:// ctrl + break
case CTRL_CLOSE_EVENT:// 关闭控制台
Result = SetEvent(hExit); // 如果操作成功,则返回非零值,否则为0
printf("(如果操作成功,则返回非零值,否则为0)---%d\r\n",Result);
return TRUE;
default:
return FALSE;
}
return FALSE;
}
void main()
{
BOOL Result = FALSE;
Result = SetConsoleCtrlHandler((PHANDLER_ROUTINE)HandlerRounte,TRUE);
if(Result == TRUE)
{
run();
}
}
到此为止,我们基本可以控制控制台的退出事件了,当然还有系统注销、用户切换或者系统关机其他控制台事件,最初的问题也基本解决。
关键词:SetConsoleCtrlHandler,控制台事件
注:版权所有,请勿用于商业用途,转载请注明原文地址。本人保留所有权利。