Friday, August 14, 2015

How to Capture Control Events (eg. Ctrl-C) in a C/C++ Windows Console App

I was recently working on a tool of mine that implements a RPC client for my RPC server.  I wanted to let the win32 console app run until the user hits ctrl-c or ctrl-break.  I guess you can also do something like getchar, but I didn't want to.  Also, I could just let the user hit ctrl-c and it would kill the app.  I do use RPC handle rundowns to clean up the RPC server state; however, I wanted to clean up and exit "safely" within the app.

So, without further ado, here is the code for using SetConsoleCtrlHandler in Windows for handling console control signals.

There are a few caveats you need to think about though when implementing control handlers.  For some events, there may be other handlers already registered, so your's may not get called.  This happens, for instance, when using a debugger.  Processes with a window message pump will also get some events through the pump that are not synchronized the process signal handler.

 
/*--
    User mode only.
--*/

#include "precomp.h"

HANDLE g_hControlEvent = NULL;

///// ctl event handling
BOOL WINAPI ControlHandlerCallback(
    _In_ DWORD dwCtrlType)
{
    HRESULT hr = S_OK;
    BOOL bControlEventHandled = FALSE;

    wprintf(L"got event %i\n", dwCtrlType);

    switch (dwCtrlType) {
    case CTRL_C_EVENT:
    case CTRL_BREAK_EVENT:

        if (!SetEvent(g_hControlEvent)) {
            hr = HRESULT_FROM_WIN32(GetLastError());
            NT_ASSERT(hr == S_OK);
        }

        if (S_OK == hr) {
            bControlEventHandled = TRUE;
        }

        break;
    CTRL_CLOSE_EVENT:
    CTRL_LOGOFF_EVENT:
    CTRL_SHUTDOWN_EVENT:
    default:
        break;
    }

    return bControlEventHandled;
}  // ControlHandlerCallback

HRESULT RegisterControlHandler()
{
    HRESULT hr = S_OK;

    g_hControlEvent = NULL;
    g_hControlEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (!g_hControlEvent) {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto exit;
    }

    if (!SetConsoleCtrlHandler(ControlHandlerCallback, TRUE)) {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto exit;
    }

    wprintf(L"press ctrl+c to stop\n");

exit:

    return hr;
}  // RegisterControlHandler

HRESULT UnregisterControlHandler()
{
    HRESULT hr = S_OK;

    if (!SetConsoleCtrlHandler(ControlHandlerCallback, FALSE)) {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto exit;
    }

    if (!CloseHandle(g_hControlEvent)) {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    g_hControlEvent = NULL;

exit:

    return hr;
}  // UnregisterControlHandler

HRESULT WaitForControlEvent()
{
    HRESULT hr = S_OK;

    if (!g_hControlEvent) {
        // called before registring
        hr = E_UNEXPECTED;
        goto exit;
    }

    if (WAIT_OBJECT_0 != WaitForSingleObject(g_hControlEvent, INFINITE)) {
        hr = E_FAIL;
        goto exit;
    }

exit:

    return hr;
}  // WaitForControlEvent

No comments:

Post a Comment