I suppose that the press of CTRL+C should be considered a signal.
Yes, but maybe not. Windows supports only a limited set of signals. And of those, SIG_INT is a special case. In fact, the documentation says, "SIG_INT is not supported in any Win32 in application."
SetConsoleCtrlHandler gives console applications the ability to handle events that would be normally be SIG_INT signals on other systems, but, as far as I can tell, it's doesn't use the signal architecture.
The documentation for SetConsoleCtrlHandler and the handler routine callback does not list many limitations on what you can do in the callback.
It does warn that the callback will be executed in a "new" thread. (It appears to queue to a thread pool, so it's not always a new thread.) Thus it's vital that any data that needs to be accessed by the main program and the callback (or by different invocations of the callback) must be synchronized.
But give that it happens in a separate thread, I suspect that, unlike an actual Windows signal handler, calling something like GetTickCount from a console control handler is fair game.
I would still strive to return from the handler as quickly as possible. Despite running in a separate thread, it's unclear under what circumstances the main thread might block on the callback.
Handlers for signals could interrupt your console control handler, but they are subject to the restrictions, so there shouldn't be any interference. From the signal handler's point of view, your console control handler is just like all the other code in your application that could be interrupted.
It seems unlikely the console subsystem would invoke your callback while an actual signal handler is executing. And, if it did, it would be on a separate thread.
#include <Windows.h>
#include <consoleapi.h> // must come after Windows.h
#include <atomic>
#include <print>
std::atomic_bool thread_was_reused = false;
std::atomic<DWORD> last_ctrl_c_tick_count = 0;
std::atomic_bool last_ctrl_c_was_double = false;
BOOL WINAPI OnCtrlEvent(DWORD event_type) {
static bool called_on_this_thread_before = false;
thread_was_reused.store(called_on_this_thread_before);
called_on_this_thread_before = true;
if (event_type == CTRL_C_EVENT) {
auto const tick = ::GetTickCount();
auto const delta = tick - last_ctrl_c_tick_count.load();
last_ctrl_c_was_double.store(delta < 250);
last_ctrl_c_tick_count.store(tick);
return TRUE;
}
return FALSE;
}
int main() {
::SetConsoleCtrlHandler(OnCtrlEvent, TRUE);
::GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
::GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
std::print("Two rapid Ctrl+C events were generated.\n");
std::print("thread was re-used: {}\n", thread_was_reused.load());
std::print("last Ctrl+C was a double: {}\n", last_ctrl_c_was_double.load());
std::print("last Ctrl+C tick count: {}\n", last_ctrl_c_tick_count.load());
::Sleep(1234);
::GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
std::print("\nA single Ctrl+C event was generated.\n");
std::print("thread was re-used: {}\n", thread_was_reused.load());
std::print("last Ctrl+C was a double: {}\n", last_ctrl_c_was_double.load());
std::print("last Ctrl+C tick count: {}\n", last_ctrl_c_tick_count.load());
return 0;
}
GetTickCount?