我的电脑上每天会跑一大堆控制台程序,于是管理这些程序的运行就成了一个问题。或者说你可能也在考虑启动一个控制台程序来完成某些特定的任务。
如果我们需要结束掉这个控制台程序怎么做呢?直接杀进程吗?这样很容易出问题。我正在使用的一个控制台程序会写文件,如果直接杀进程可能导致数据没能写入到文件。所以本文介绍如何使用 .NET/C# 代码向控制台程序发送 Ctrl C
来安全地结束掉程序。
用 Ctrl C 结束控制台程序
如果直接用 Process.Kill
杀掉进程,进程可能来不及保存数据。所以无论是窗口程序还是控制台程序,最好都让控制台程序自己去关闭。
▲ 使用 Process.Kill
结束程序,程序退出代码是 -1
▲ 使用 Ctrl C
结束程序,程序退出代码是 0
Ctrl C 信号
Windows API 提供了方法可以将当前进程与目标控制台进程关联起来,这样我们便可以向自己发送 Ctrl C
信号来结束掉关联的另一个控制台进程。
关联和取消关联的方法是下面这两个,AttachConsole
和 FreeConsole
:
1 2 3 4 5 | [DllImport("kernel32.dll")] private static extern bool AttachConsole(uint dwProcessId); [DllImport("kernel32.dll")] private static extern bool FreeConsole(); |
---|
不过,当发送 Ctrl C
信号的时候,不止我们希望关闭的控制台程序退出了,我们自己程序也是会退出的(即便我们自己是一个 GUI 程序)。所以我们必须先组织自己响应 Ctrl C
信号。
需要用到另外一个 API:
1 2 3 4 5 6 7 8 9 10 11 12 13 | [DllImport("kernel32.dll")] private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate? HandlerRoutine, bool Add); enum CtrlTypes : uint { CTRL_C_EVENT = 0, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT = 5, CTRL_SHUTDOWN_EVENT } private delegate bool ConsoleCtrlDelegate(CtrlTypes CtrlType); |
---|
不过,因为我们实际上并不需要真的对 Ctrl C
进行响应,只是单纯临时禁用以下,所以我们归这个委托传入 null
就好了。
最后,也是最关键的,就是发送 Ctrl C
信号了:
1 2 3 | [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId); |
---|
下面,我将完整的代码贴出来。
全部源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | using System; using System.Diagnostics; using System.Runtime.InteropServices; namespace Walterlv.Fracture.Utils { /// <summary> /// 提供与控制台程序的交互。 /// </summary> public class ConsoleInterop { /// <summary> /// 关闭控制台程序。 /// </summary> /// <param name="process">要关闭的控制台程序的进程实例。</param> /// <param name="timeoutInMilliseconds">如果不希望一直等待进程自己退出,则可以在此参数中设置超时。你可以在超时未推出候采取强制杀掉进程的策略。</param> /// <returns>如果进程成功退出,则返回 true;否则返回 false。</returns> public static bool StopConsoleProgram(Process process, int? timeoutInMilliseconds = null) { if (process is null) { throw new ArgumentNullException(nameof(process)); } if (process.HasExited) { return true; } // 尝试将我们自己的进程附加到指定进程的控制台(如果有的话)。 if (AttachConsole((uint)process.Id)) { // 我们自己的进程需要忽略掉 Ctrl C 信号,否则自己也会退出。 SetConsoleCtrlHandler(null, true); // 将 Ctrl C 信号发送到前面已关联(附加)的控制台进程中。 GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0); // 拾前面已经附加的控制台。 FreeConsole(); bool hasExited; // 由于 Ctrl C 信号只是通知程序关闭,并不一定真的关闭。所以我们等待一定时间,如果仍未关闭,则超时不处理。 // 业务可以通过判断返回值来角是否进行后续处理(例如强制杀掉)。 if (timeoutInMilliseconds == null) { // 如果没有超时处理,则一直等待,直到最终进程停止。 process.WaitForExit(); hasExited = true; } else { // 如果有超时处理,则超时候返回。 hasExited = process.WaitForExit(timeoutInMilliseconds.Value); } // 重新恢复我们自己的进程对 Ctrl C 信号的响应。 SetConsoleCtrlHandler(null, false); return hasExited; } else { return false; } } [DllImport("kernel32.dll")] private static extern bool AttachConsole(uint dwProcessId); [DllImport("kernel32.dll")] private static extern bool FreeConsole(); [DllImport("kernel32.dll")] private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate? HandlerRoutine, bool Add); [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId); enum CtrlTypes : uint { CTRL_C_EVENT = 0, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT = 5, CTRL_SHUTDOWN_EVENT } private delegate bool ConsoleCtrlDelegate(CtrlTypes CtrlType); } } |
---|
如何使用
现在,我们可以通过调用 ConsoleInterop.StopConsoleProgram(process)
来安全地结束掉一个控制台程序。
当然,为了处理一些意外的情况,我把超时也加上了。下面的用法演示超时 2 秒候程序还没有退出,则强杀。
1 2 3 4 5 6 7 8 9 10 | if (!ConsoleInterop.StopConsoleProgram(process, 2000)) { try { process.Kill(); } catch (InvalidOperationException e) { } } |
---|
参考资料
- signals - Can I send a ctrl-C (SIGINT) to an application on Windows? - Stack Overflow
- Stopping command-line applications programatically with Ctrl-C event from .Net – a working demo - Nemo’s Realms
- AttachConsole function - Windows Console - Microsoft Docs
- SetConsoleCtrlHandler function - Windows Console - Microsoft Docs
本文会经常更新,请阅读原文: https://blog.walterlv.com/post/shutdown-a-console-program-safely-using-ctrl-c.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://blog.walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 ([email protected]) 。