【问题标题】:Handle CTRL+C on Win32在 Win32 上处理 CTRL+C
【发布时间】:2013-08-19 22:05:56
【问题描述】:

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

基本上我的程序如下所示:(基于另一个问题:Windows Ctrl-C - Cleaning up local stack objects in command line app

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 中,函数处理程序在另一个线程中运行,因此当处理程序/线程结束其执行时,主线程处于休眠状态。大概这就是进程中断的原因吧?

【问题讨论】:

  • SetConsoleCtrlHandler 返回什么?
  • 你确定ConsoleHandler 函数运行了吗?如果你在调试器中运行程序并在其中设置断点,它会在断点处停止吗?
  • 另外,您应该使用 BOOL 作为 retval,并使用 DWORD 作为 consoleHandler 的参数。另外,FALSE/TRUE 里面...
  • 而你在调用SetConsoleCtrlHandler之前声明consoleHandler函数的函数原型?
  • 那如何检查循环后的代码没有执行呢?如果您在该代码处设置断点并在调试器中运行,它会停在那里吗?

标签: c++ winapi console-application


【解决方案1】:

以下代码适用于我:

#include <windows.h> 
#include <stdio.h> 

BOOL WINAPI consoleHandler(DWORD signal) {

    if (signal == CTRL_C_EVENT)
        printf("Ctrl-C handled\n"); // 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;
}

【讨论】:

    【解决方案2】:

    根据您的具体要求,您有多种选择。如果你只是想忽略 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]+C\n");
            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("Starting\n");
        while ( isRunnung ) {
            Sleep(0);
        }
        printf("Ending\n");
    
       return 0;
    }
    

    这个应用程序的输出是:

    Starting
    [Ctrl]+C
    Ending
    

    请注意,无论主while-loop 中的代码如何,都会执行清理代码。信号处理程序形成一个链表,其中处理程序函数在最后注册、第一次调用的基础上被调用,直到其中一个处理程序返回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 位进行了编译,发布和调试配置。

    【讨论】:

      【解决方案3】:

      根据the documentation,当处理程序(声明错误,顺便说一句)接收到CTRL_CLOSE_EVENTCTRL_LOGOFF_EVENTCTRL_SHUTDOWN_EVENT 信号时,处理程序退出后进程终止。要执行您正在尝试的操作,您应该将清理代码移动到处理程序本身。

      【讨论】:

      • 看起来我的程序没有接收到这些事件,而只有一个CTRL_C_EVENT。刚刚在我的处理函数中使用switch 进行了测试,并从控制台按 CTRL+C。
      【解决方案4】:

      实际上 Win32 对 POSIX 信号有一些仿真,因此您只需执行以下操作,即可在 Windows 和 POSIX(Linux、*BSD 等)之间移植:

      #include <signal.h>
      #include <stdio.h>
      #include <stdlib.h>
      
      static void sigHandler(int sig) {
          printf("Signal %d received, exiting\n", 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;
      }
      

      【讨论】:

        【解决方案5】:

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

        (另外,请注意对于 Codeblock,您需要从项目/bin/debug 目录中的 Codeblocks 程序目录 ('C:\Program Files\CodeBlocks*.dll') 中复制所有 DLL,以获取控制台应用程序从命令行运行...)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2022-01-14
          • 1970-01-01
          • 2013-12-05
          • 1970-01-01
          • 1970-01-01
          • 2011-01-29
          • 1970-01-01
          相关资源
          最近更新 更多