捕获控制台程序关闭事件(SetConsoleCtrlHandler

本文介绍如何使用SetConsoleCtrlHandler方法捕获控制台程序关闭事件,包括Ctrl+C、Ctrl+Break、窗口关闭等,并提供了通过回调函数进行相应操作的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有时候运行在服务器上的控制台程序,需要记录详细的运行日志,这就需要对程序关闭进行日志记录,以便能根据日志了解程序的运行状况。比如正在运行的程序被人不小心关闭了,导致最终任务没有运行成功,这时日志也没有错误记录,对分析原因造成不便,记录了关闭事件日志后就能了解到这种情况是程序被终止了。这样注意通过消息钩子来实现,通过调用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。

<think>好的,用户的问题是关于如何在C#控制台应用程序中防止被意外关闭。我需要先理解用户的具体需求。控制台程序意外退出可能是指用户按了Ctrl+C或者关闭控制台窗口,或者程序本身因为未处理的异常而崩溃。用户可能希望在这些情况下保持程序运行,或者优雅地处理这些退出事件。 首先,我应该回忆一下C#中处理控制台程序退出的方法。记得有Console.CancelKeyPress事件,可以监听Ctrl+C或者Ctrl+Break信号。这时候可以取消默认的关闭行为,比如设置e.Cancel为true,这样程序就不会立即退出了。 另外,处理未捕获的异常也很重要。可以通过AppDomain.CurrentDomain.UnhandledException事件捕获未处理的异常,记录日志或者进行恢复操作,避免程序直接崩溃。 用户提到的是“误关闭”,可能还指防止窗口被手动关闭。但控制台窗口被用户点击关闭按钮的话,可能难以完全阻止,但可以提示用户确认或者延迟关闭。不过这方面可能比较复杂,需要调用Windows API,比如通过SetConsoleCtrlHandler来注册处理函数,处理CTRL_CLOSE_EVENT事件。这时候可以返回TRUE来阻止关闭,但实际效果可能有限,因为系统可能会强制关闭。 此外,可能还需要考虑程序本身的逻辑,比如使用循环保持程序运行,直到用户输入特定的退出命令。这样即使有意外操作,程序仍然会继续运行,直到正确退出。 需要检查引用的资料是否有相关的内容。引用[1]和[2]似乎与代码链接和标记有关,可能不相关。引用[3]是关于流参数的设置,引用[4]提到C#4.0的可选参数,这些可能与当前问题无关。所以主要依靠自己的知识库来回答。 总结可能的解决方案: 1. 处理Ctrl+C事件,取消默认的关闭行为。 2. 处理未处理的异常,防止程序崩溃。 3. 使用Windows API处理控制台关闭事件。 4. 程序主循环设计,等待正确退出指令。 接下来需要验证这些方法的正确性。比如,Console.CancelKeyPress的使用示例,如何设置事件处理函数。在事件处理中,可以提示用户确认退出,或者忽略信号。但需要注意,如果强制取消退出,可能会导致用户无法正常结束程序,需要合理设计。 对于未处理的异常,处理之后可能还是需要决定是否继续运行,或者记录错误后退出。这里需要明确用户的需求,是希望程序在异常后继续运行,还是优雅退出。 关于Windows API的方法,可能需要使用kernel32.dll中的SetConsoleCtrlHandler函数,处理各种控制台事件,如CTRL_CLOSE_EVENT。这需要编写P/Invoke代码来调用原生函数。 另外,保持程序的主线程活动,比如使用一个循环,等待用户输入exit命令后才退出,这样即使有其他中断,程序仍会继续运行。但需要避免阻塞主线程,合理处理输入。 现在需要将这些方法整理成步骤,并给出示例代码。同时需要注意代码的正确性,比如事件处理函数的注册,多线程问题等。 可能的问题点:当用户点击控制台窗口的关闭按钮时,默认行为是发送CTRL_CLOSE_EVENT,此时处理函数可以延迟关闭,但系统可能在一定时间后强制终止程序。因此,无法完全阻止,但可以提示用户或执行清理操作。 综上所述,最佳实践应包括处理取消键事件、异常处理,以及可能的主循环设计,确保程序只有在特定条件下退出。</think>在C#中防止控制台应用程序意外退出的最佳实践,可从以下方面入手: ### 1. 拦截控制台关闭信号 使用$Console.CancelKeyPress$事件捕获Ctrl+C或Ctrl+Break信号: ```csharp Console.CancelKeyPress += (sender, e) => { e.Cancel = true; // 阻止默认关闭行为 Console.WriteLine("已拦截退出信号,输入'exit'退出程序"); }; ``` ### 2. 处理未捕获异常 通过$AppDomain.UnhandledException$全局异常处理: ```csharp AppDomain.CurrentDomain.UnhandledException += (sender, e) => { File.WriteAllText("crash.log", $"致命错误: {e.ExceptionObject}"); Console.WriteLine("程序遇到错误,已记录日志"); Environment.Exit(1); // 可选:主动退出程序 }; ``` ### 3. 阻止窗口关闭事件(需Windows API) 调用Windows API处理控制台关闭事件: ```csharp using System.Runtime.InteropServices; [DllImport("kernel32.dll")] static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add); private delegate bool ConsoleCtrlDelegate(int eventType); static void Main() { SetConsoleCtrlHandler(HandlerMethod, true); while (true) Thread.Sleep(1000); } private static bool HandlerMethod(int eventType) { if (eventType == 2) { // CTRL_CLOSE_EVENT Console.WriteLine("请通过输入'exit'正常退出"); return true; // 返回true表示已处理该事件 } return false; } ``` ### 4. 实现安全退出机制 使用主循环控制程序生命周期: ```csharp static bool _isRunning = true; static void Main() { while (_isRunning) { var input = Console.ReadLine(); if (input == "exit") { _isRunning = false; Console.WriteLine("正在保存数据..."); Thread.Sleep(1000); } } } ``` ### 组合方案示例 ```csharp class Program { [DllImport("kernel32.dll")] private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add); private delegate bool ConsoleCtrlDelegate(int eventType); static void Main() { // 注册事件处理 Console.CancelKeyPress += OnCancelKeyPress; AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; SetConsoleCtrlHandler(OnConsoleEvent, true); // 主业务逻辑 while (true) { Console.Write("输入命令: "); var cmd = Console.ReadLine(); if (cmd == "exit") break; } } private static void OnCancelKeyPress(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; Console.WriteLine("\n请使用'exit'命令退出程序"); } private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) { var ex = (Exception)e.ExceptionObject; File.AppendAllText("errors.log", $"{DateTime.Now}: {ex}\n"); } private static bool OnConsoleEvent(int eventType) { if (eventType == 2) { // CTRL_CLOSE_EVENT Console.WriteLine("检测到窗口关闭事件,正在保存数据..."); Thread.Sleep(2000); // 模拟数据保存 } return false; } } ``` ### 注意事项 1. 窗口关闭事件最多延迟5秒,之后系统会强制终止进程[^1] 2. 异常处理后应谨慎决定是否继续运行,避免状态不一致 3. 关键操作建议使用事务机制保证数据完整性[^3] 4. 对于需要高性能的场景,建议采用异步事件处理[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值