有时候运行在服务器上的控制台程序,需要记录详细的运行日志,这就需要对程序关闭进行日志记录,以便能根据日志了解程序的运行状况。比如正在运行的程序被人不小心关闭了,导致最终任务没有运行成功,这时日志也没有错误记录,对分析原因造成不便,记录了关闭事件日志后就能了解到这种情况是程序被终止了。这样注意通过消息钩子来实现,通过调用WIN32 API SetConsoleCtrlHandler方法来实现,具体代码如下:
using System;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace ConsoleColsed
{
public delegate bool ConsoleCtrlDelegate(int ctrlType);
class Program
{
[DllImport("kernel32.dll")]
private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate HandlerRoutine, bool Add);
//当用户关闭Console时,系统会发送次消息
private const int CTRL_CLOSE_EVENT = 2;
//Ctrl+C,系统会发送次消息
private const int CTRL_C_EVENT = 0;
//Ctrl+break,系统会发送次消息
private const int CTRL_BREAK_EVENT = 1;
//用户退出(注销),系统会发送次消息
private const int CTRL_LOGOFF_EVENT = 5;
//系统关闭,系统会发送次消息
private const int CTRL_SHUTDOWN_EVENT = 6;
static void Main(string[] args)
{
Program cls = new Program();
//Console.ReadKey();
}
public Program()
{
ConsoleCtrlDelegate consoleDelegete = new ConsoleCtrlDelegate(HandlerRoutine);
bool bRet = SetConsoleCtrlHandler(consoleDelegete, true);
if (bRet == false) //安装事件处理失败
{
Debug.WriteLine("error");
}
else
{
Console.WriteLine("ok");
Console.Read();
}
}
private static bool HandlerRoutine(int ctrlType)
{
switch(ctrlType)
{
case CTRL_C_EVENT:
MessageBox.Show("C");
break;
case CTRL_BREAK_EVENT:
MessageBox.Show("BREAK");
break;
case CTRL_CLOSE_EVENT:
MessageBox.Show("CLOSE");
break;
case CTRL_LOGOFF_EVENT:
break;
case CTRL_SHUTDOWN_EVENT:
break;
}
//return true;//表示阻止响应系统对该程序的操作
return false;//忽略处理,让系统进行默认操作
}
}
}
控制台程序足够简洁,但是,经常会点错而误关闭。而且,如果系统关闭,或者用户注销,这时候任务还没完成的话,前面的运算电费就白出了。
有没有办法和WinForm一样,对控制台的退出事件进行控制呢?有的!
引入下面的函数
1
public
delegate
bool
HandlerRoutine(
int
dwCtrlType);
2
3
[DllImport(
"
kernel32.dll
"
, CharSet
=
CharSet.Auto)]
4
public
static
extern
bool
SetConsoleCtrlHandler(HandlerRoutine HandlerRoutine,
bool
add);
委托HandlerRoutine,就是把函数的指针传递给系统API函数SetConsoleCtrlHandler。这是个典型的回调函数。
然后在Main方法中调用
Program p
=
new
Program();
if
(
!
SetConsoleCtrlHandler(p.HandlerRoutineMethod,
true
))
{
Console.WriteLine(
"
Unable to install event handler!\n
"
);
}
const
int
CTRL_C_EVENT
=
0
;
const
int
CTRL_BREAK_EVENT
=
1
;
const
int
CTRL_CLOSE_EVENT
=
2
;
const
int
CTRL_LOGOFF_EVENT
=
5
;
const
int
CTRL_SHUTDOWN_EVENT
=
6
;
public
bool
HandlerRoutineMethod(
int
dwCtrlType)
{
Console.WriteLine(dwCtrlType.ToString());
switch
(dwCtrlType)
{
case
CTRL_C_EVENT:
return
true
;
case
CTRL_BREAK_EVENT:
return
false
;
case
CTRL_CLOSE_EVENT:
Console.WriteLine(
"
确实要退出程序么?如果需要退出,请输入'exit'。
"
);
return
true
;
case
CTRL_LOGOFF_EVENT:
//
用户退出
return
false
;
case
CTRL_SHUTDOWN_EVENT:
//
系统关闭
return
false
;
}
return
true
;
}
HandlerRoutineMethod函数,就是系统的真实回调。如果返回的结果为false,则程序关闭,否则,不会关闭。
这里只在CTRL_BREAK_EVENT(按下Ctrl+Break),CTRL_LOGOFF_EVENT用户退出,和系统关闭
CTRL_SHUTDOWN_EVENT,事件时,调用了return false,也就是说,这个时候会关闭。但是在关闭之前我们可以做一些操作。
试想这样的情况,这个任务可能需要运行很长时间,要能在程序关闭的时候有个保存当前进度的方法,那么就可以采用以上操作了。
来看个完整的代码
1
class
Program
2
{
3
static
void
Main(
string
[] args)
4
{
5
Program p
=
new
Program();
6
if
(
!
SetConsoleCtrlHandler(p.HandlerRoutineMethod,
true
))
7
{
8
Console.WriteLine(
"
无法注册系统事件!\n
"
);
9
}
10
11
while
(
true
)
12
{
13
string
s
=
Console.ReadLine();
14
if
(s
==
"
exit
"
)
15
GenerateConsoleCtrlEvent(p.CTRL_BREAK_EVENT
,
0
);
16
}
17
}
18
19
const
int
CTRL_C_EVENT
=
0
;
20
const
int
CTRL_BREAK_EVENT
=
1
;
21
const
int
CTRL_CLOSE_EVENT
=
2
;
22
const
int
CTRL_LOGOFF_EVENT
=
5
;
23
const
int
CTRL_SHUTDOWN_EVENT
=
6
;
24
25
public
bool
HandlerRoutineMethod(
int
dwCtrlType)
26
{
27
Console.WriteLine(dwCtrlType.ToString());
28
switch
(dwCtrlType)
29
{
30
case
CTRL_C_EVENT:
31
return
true
;
32
case
CTRL_BREAK_EVENT:
33
Save();
34
return
false
;
35
case
CTRL_CLOSE_EVENT:
36
Console.WriteLine(
"
确实要退出程序么?如果需要退出,请输入'exit'。
"
);
37
return
true
;
38
case
CTRL_LOGOFF_EVENT:
39
//
用户退出
40
Save();
41
return
false
;
42
case
CTRL_SHUTDOWN_EVENT:
43
//
系统关闭
44
Save();
45
return
false
;
46
}
47
return
true
;
48
}
49
50
void
Save()
51
{
52
//
保存当前进度
53
}
54
55
public
delegate
bool
HandlerRoutine(
int
dwCtrlType);
56
57
[DllImport(
"
kernel32.dll
"
, CharSet
=
CharSet.Auto)]
58
public
static
extern
bool
SetConsoleCtrlHandler(HandlerRoutine HandlerRoutine,
bool
add);
59
60
[DllImport(
"
kernel32.dll
"
, CharSet
=
CharSet.Auto)]
61
public
static
extern
bool
GenerateConsoleCtrlEvent(
int
code,
int
value);
62
}
先 是用
SetConsoleCtrlHandler方法,设置了HandlerRoutineMethod为他的回调函数。那么当有事件过来的时候,会先调用这个方法。这个方法return false,则窗体关闭,true,则窗体不关闭。
GenerateConsoleCtrlEvent方法是通知系统事件的。我们这里假设,只有用户输入exit,或者按Ctrl+Break的时候程序退出。对于用户退出和系统关闭,只是保存当前进度。
在while 循环中,如果用户输入exit命令,则通知系统调用回调函数HandlerRoutineMethod,调用的事件是Ctrl+Break。