在Win32上处理CTRL+C

Handle CTRL+C on Win32

本文关键字:CTRL+C 处理 Win32      更新时间:2023-10-16

我在Win32 C++控制台程序中处理CTRL+C事件时遇到一些问题。

基本上,我的程序看起来是这样的:(基于另一个问题:Windows Ctrl-C-清理命令行应用程序中的本地堆栈对象)

bool running;
int main() {
    running = true;
    SetConsoleCtrlHandler((PHANDLER_ROUTINE) consoleHandler, TRUE);
    while (running) {
       // do work
       ...
    }
    // do cleanup
    ...
    return 0;
}
bool consoleHandler(int signal) {
    if (signal == CTRL_C_EVENT) {
        running = false;
    }
    return true;
}

问题是根本没有执行清理代码。

在执行处理程序函数后,进程终止,但不执行主循环后的代码。怎么了?

编辑:根据要求,这是一个类似于我的程序的最小测试用例:http://pastebin.com/6rLK6BU2

我的输出中没有"测试清理指令"字符串。

我不知道这是否重要,我正在使用MinGW进行编译。


编辑2:测试用例程序的问题是使用了Sleep()函数。如果没有它,程序将按预期运行。

在Win32中,函数处理程序在另一个线程中运行,因此当处理程序/线程结束执行时,主线程处于休眠状态。可能这就是流程中断的原因?

以下代码适用于我:

#include <windows.h> 
#include <stdio.h> 
BOOL WINAPI consoleHandler(DWORD signal) {
    if (signal == CTRL_C_EVENT)
        printf("Ctrl-C handledn"); // do cleanup
    return TRUE;
}
int main()
{
    running = TRUE;
    if (!SetConsoleCtrlHandler(consoleHandler, TRUE)) {
        printf("nERROR: Could not set control handler"); 
        return 1;
    }
    while (1) { /* do work */ }
    return 0;
}

根据您的具体要求,您有许多选项。如果您只想忽略Ctrl+C,您可以调用SetConsoleCtrlHandler,将NULL作为HandlerRoutine参数:

int _tmain(int argc, _TCHAR* argv[])
{
    SetConsoleCtrlHandler(NULL, TRUE);
    // do work
    return 0;
}

这将删除所有信号处理程序。要终止此应用程序,您必须实现自定义逻辑来确定何时关闭。

如果您想处理Ctrl+C,您有两个选项:设置信号处理程序或将键盘输入传递到常规键盘处理。

设置一个处理程序与上面的代码类似,但您提供自己的实现,而不是将NULL作为处理程序传递。

#include <windows.h>
#include <stdio.h>
volatile bool isRunnung = true;
BOOL WINAPI HandlerRoutine(_In_ DWORD dwCtrlType) {
    switch (dwCtrlType)
    {
    case CTRL_C_EVENT:
        printf("[Ctrl]+Cn");
        isRunnung = false;
        // Signal is handled - don't pass it on to the next handler
        return TRUE;
    default:
        // Pass signal on to the next handler
        return FALSE;
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    SetConsoleCtrlHandler(HandlerRoutine, TRUE);
    printf("Startingn");
    while ( isRunnung ) {
        Sleep(0);
    }
    printf("Endingn");
   return 0;
}

此应用程序的输出为:

Starting
[Ctrl]+C
Ending

请注意,无论主while循环中的代码是什么,都会执行清理代码。信号处理程序形成一个链表,其中处理程序函数在最后注册、第一次调用的基础上被调用,直到其中一个处理程序返回TRUE。如果没有一个处理程序返回TRUE,则调用默认处理程序。控制台的默认处理程序在处理Ctrl+C时调用ExitProcess

如果您想阻止任何预处理并将Ctrl+C处理为常规键盘输入,则必须通过调用SetConsoleMode来更改控制台模式。

#include <windows.h>
#include <stdio.h>
int _tmain(int argc, _TCHAR* argv[])
{
    DWORD dwMode = 0x0;
    GetConsoleMode( GetStdHandle(STD_INPUT_HANDLE), &dwMode );
    // Remove ENABLE_PROCESSED_INPUT flag
    dwMode &= ~ENABLE_PROCESSED_INPUT;
    SetConsoleMode( GetStdHandle(STD_INPUT_HANDLE), dwMode );
    while ( true ) {
        // Ctrl+C can be read using ReadConsoleInput, etc.
    }
    return 0;
}

一旦ENABLE_PROCESSED_INPUT标志被移除,Ctrl+C将不再由系统处理,并像常规键盘输入一样传递到控制台。可以使用ReadConsoleInputReadFile读取。

免责声明:以上内容已在Windows 8 64位上测试,编译为32和64位,发布和调试配置

根据文档,当处理程序(被声明为错误的BTW)接收到CTRL_CLOSE_EVENTCTRL_LOGOFF_EVENTCTRL_SHUTDOWN_EVENT信号时,处理程序退出后进程终止。要执行您正在尝试的操作,您应该将清理代码移动到处理程序本身中。

实际上Win32对POSIX信号进行了一些模拟,所以您只需执行以下操作,即可在Windows和POSIX(Linux、*BSD等)之间移植:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
static void sigHandler(int sig) {
    printf("Signal %d received, exitingn", sig);
    exit(0);
};
int main() {
    signal(SIGINT, sigHandler);
#ifdef SIGBREAK
    signal(SIGBREAK, sigHandler); // handles Ctrl-Break on Win32
#endif
    signal(SIGABRT, sigHandler);
    signal(SIGTERM, sigHandler);
    printf("Press Ctrl-C to exit ...n");
    for (;;) {
        getchar();
    }
    return 0;
}

尝试直接从windows命令行运行控制台应用程序,而不使用C++IDE。我注意到,如果我从code::BLocks C++IDE运行,您的代码将无法工作。。。但如果我从命令行运行它,它就会工作。。。。

(此外,请注意,对于Codeblock,您需要从项目/bin/debug目录中的Codeblock程序目录("C:\program Files\Codeblocks*.dll")中复制所有dll,以便从命令行运行控制台应用程序…)