关闭控制台窗口时保持后台服务程序运行的解决方案咨询
解决控制台窗口关闭导致后台程序终止的问题
当你通过AllocConsole创建控制台窗口后,默认情况下点击窗口红叉或按下Ctrl+C会触发进程终止信号,导致整个后台服务跟着退出。要实现仅关闭控制台窗口、保留后台服务运行的效果,需要拦截这两类事件,手动控制控制台的释放逻辑。
解决方案步骤
- 拦截控制台窗口关闭事件:获取控制台窗口句柄,替换窗口过程处理
WM_CLOSE消息,调用FreeConsole而非让系统终止进程。 - 处理Ctrl+C信号:注册控制台事件回调,捕获
CTRL_C_EVENT时执行控制台关闭逻辑,忽略默认的终止行为。 - 修改日志窗口显示逻辑:在
ShowLoggingWindow中整合上述拦截逻辑,确保控制台关闭时仅释放资源。
修改后的完整代码
using System; using System.Runtime.InteropServices; using System.Windows.Forms; using System.IO; class Program { // 控制台相关API [DllImport("kernel32.dll")] private static extern bool AllocConsole(); [DllImport("kernel32.dll")] private static extern bool FreeConsole(); [DllImport("kernel32.dll")] private static extern IntPtr GetConsoleWindow(); [DllImport("kernel32.dll")] private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handlerRoutine, bool add); [DllImport("user32.dll")] private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong); [DllImport("user32.dll")] private static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); // 委托定义 private delegate bool ConsoleCtrlDelegate(uint dwCtrlType); private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); // 常量定义 private const int GWL_WNDPROC = -4; private const uint WM_CLOSE = 0x0010; private const uint CTRL_C_EVENT = 0; // 保存原窗口过程指针 private static IntPtr _originalConsoleWndProc; private static WndProcDelegate _consoleWndProcDelegate; static void Main(string[] args) { // 系统托盘图标初始化 NotifyIcon notifyIcon = new NotifyIcon(); notifyIcon.Icon = SystemIcons.Shield; notifyIcon.Visible = true; notifyIcon.Text = "Background Service"; notifyIcon.MouseClick += (s, e) => { if (e.Button == MouseButtons.Left) ShowLoggingWindow(); }; // 右键菜单设置 ContextMenuStrip trayIconMenuStrip = new ContextMenuStrip(); ToolStripMenuItem menuItem1 = new ToolStripMenuItem("Show Output"); ToolStripMenuItem menuItem2 = new ToolStripMenuItem("Exit"); trayIconMenuStrip.Items.Add(menuItem1); trayIconMenuStrip.Items.Add(menuItem2); notifyIcon.ContextMenuStrip = trayIconMenuStrip; menuItem1.Click += (s, e) => ShowLoggingWindow(); menuItem2.Click += (s, e) => Application.Exit(); // 启动消息循环 Application.Run(); } static void ShowLoggingWindow() { AllocConsole(); // 重定向标准输出 Console.SetOut(new StreamWriter(Console.OpenStandardOutput(), Console.OutputEncoding) { AutoFlush = true }); // 注册Ctrl+C事件处理 SetConsoleCtrlHandler(ConsoleCtrlHandler, true); // 获取控制台窗口句柄并替换窗口过程 IntPtr consoleWnd = GetConsoleWindow(); _consoleWndProcDelegate = ConsoleWndProc; _originalConsoleWndProc = SetWindowLongPtr(consoleWnd, GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(_consoleWndProcDelegate)); // 输出日志内容 Console.WriteLine("=== 后台服务日志 ==="); // ... 这里添加你的日志输出逻辑 Console.WriteLine("Press any key to continue..."); // 等待按键或窗口关闭 Console.ReadKey(true); // 清理资源 FreeConsole(); SetConsoleCtrlHandler(ConsoleCtrlHandler, false); } // 控制台窗口过程处理函数 private static IntPtr ConsoleWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { if (msg == WM_CLOSE) { // 手动释放控制台,阻止系统终止进程 FreeConsole(); // 恢复原窗口过程 SetWindowLongPtr(hWnd, GWL_WNDPROC, _originalConsoleWndProc); return IntPtr.Zero; } // 其他消息交给原窗口过程处理 return CallWindowProc(_originalConsoleWndProc, hWnd, msg, wParam, lParam); } // Ctrl+C事件处理函数 private static bool ConsoleCtrlHandler(uint dwCtrlType) { if (dwCtrlType == CTRL_C_EVENT) { // 关闭控制台窗口 FreeConsole(); // 返回true表示已处理该事件,阻止系统终止进程 return true; } return false; } }
代码说明
SetConsoleCtrlHandler:注册回调函数捕获Ctrl+C信号,返回true表示已处理该事件,避免系统触发进程终止。ConsoleWndProc:替换控制台窗口的默认消息处理逻辑,当收到WM_CLOSE(点击红叉)时,调用FreeConsole释放控制台资源,同时恢复原窗口过程,阻止系统终止进程。- 资源清理:在控制台关闭后,移除Ctrl+C事件处理,避免内存泄漏。
这样修改后,无论是点击红叉还是按下Ctrl+C,都只会关闭控制台窗口,后台服务会继续正常运行。
内容的提问来源于stack exchange,提问作者Sam Hill
相关产品推荐
相关产品推荐

