23

I have some problems with the handling of CTRL+C events, in a Win32 C++ console program.

Basically my program looks like this: (based on this other question: 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;
}

The problem is the cleanup code not being executed at all.

After the execution of the handler function the process is terminated, but without execute the code after the main loop. What's wrong?

EDIT: as requested, this is a minimal test case similar to my program: http://pastebin.com/6rLK6BU2

I don't get the "test cleanup-instruction" string in my output.

I don't know if this is important, I'm compiling with MinGW.


EDIT 2: The problem with the test case program is the use of the Sleep() function. Without it the program works as expected.

In Win32 the function handler runs in another thread, so when the handler/thread ends its execution the main thread is sleeping. Probably this is the cause of process interruption?

Fabrizio
  • 7,603
  • 6
  • 44
  • 104
eang
  • 1,615
  • 7
  • 21
  • 41

5 Answers5

22

The following code works for me:

#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;
}
László Papp
  • 51,870
  • 39
  • 111
  • 135
18

Depending on your specific requirements you have a number of options. If you simply want to ignore Ctrl+C you can call SetConsoleCtrlHandler passing NULL as the HandlerRoutine parameter:

int _tmain(int argc, _TCHAR* argv[])
{
    SetConsoleCtrlHandler(NULL, TRUE);

    // do work

    return 0;
}

This removes all signal handlers. To terminate this application you have to implement custom logic to determine when to shut down.

If you want to handle Ctrl+C you have two options: Set up a handler for the signal or pass the keyboard input on to regular keyboard handling.

Setting up a handler is similar to the code above, but instead of passing NULL as the handler you provide your own implementation.

#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;
}

The output of this application is:

Starting
[Ctrl]+C
Ending

Note that cleanup code is executed, regardless of the code inside the main while-loop. Signal handlers form a linked list, where the handler functions are called on a last-registered, first-called basis until one of the handlers returns TRUE. If none of the handlers returns TRUE, the default handler is called. The default handler for a console calls ExitProcess when processing Ctrl+C.

If you want to prevent any preprocessing and handle Ctrl+C as regular keyboard input instead you have to change the console mode by calling 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;
}

Once the ENABLE_PROCESSED_INPUT flag is removed Ctrl+C is no longer processed by the system and passed to the console like regular keyboard input. It can be read using ReadConsoleInput or ReadFile.

Disclaimer: The above has been tested on Windows 8 64bit, compiled for 32 and 64 bit, Release and Debug configurations.

IInspectable
  • 46,945
  • 8
  • 85
  • 181
11

According to the documentation, when the handler (which is declared wrong, BTW) receives a CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, or CTRL_SHUTDOWN_EVENT signal, the process is terminated after the handler exits. To do what you are attempting, you are supposed to move your cleanup code into the handler itself.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • It looks like my program is not receiving these events, but only a `CTRL_C_EVENT`. Tested just now with a `switch` in my handler function, and hitting CTRL+C from console. – eang Aug 17 '13 at 17:46
6

Actually Win32 has some emulation for POSIX signals, so you can just do the following, which will be portable between Windows and POSIX (Linux, *BSD, etc.):

#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;
}
rustyx
  • 80,671
  • 25
  • 200
  • 267
0

Try running your console application directly from the windows command line without using the C++ IDE. I noticed that your code doesn't work if I run from the Code::BLocks C++ IDE... but it works if I run it from the command line....

(Also, note for Codeblock you need to copy all of the DLLs from the Codeblocks program directory ('C:\Program Files\CodeBlocks*.dll') in your project/bin/debug directory to get the console application to run from the command line...)

Bimo
  • 5,987
  • 2
  • 39
  • 61