Coding with Titans

so breaking things happens constantly, but never on purpose

Sending Ctrl-C signal to another application on Windows

I have a few command-line applications running on Windows. I haven’t written them myself, they are just ports of some Linux/Unix utilities. And there are situations, when I simply want them to stop or to pay more attention to me. Sure, I could make them to exit using TerminateProcess from my manager application, but this seems to be quite rude, even in the IT world. Much cleaner and nicer way would be to send them a polite notification, that would wake them up, allow release used resources (as some communicate with remote servers) and handle the exit themselves. Thankfully there is such a notification - console Ctrl-C signal (not the copying shortcut!).

As it turned out, it is a very popular subject on the Internet, where of course 99% of the solutions are very dirty hacks (like SendSignal or its improvements), that just work on some versions of Windows by accident etc. They tend to call some kernel-level handlers by using remote threads. OMG!

Another approach could be using GenerateConsoleCtrlEvent API. It does all what is needed, but the MSDN documentation doesn’t explain few caveats. To make it actually work, the process that calls the function must share the console with the one we want to send it, otherwise it won’t work. That also means the sender will receive that signal, too. By default on Windows it simply exits the process, if you have a GUI application, what might look like, it doesn’t work and crashed. Additionally on Windows all child processes inherit the parent’s console handle, if the parent is created with CREATE_NEW_PROCESS_GROUP flag. My first approach (which I borrowed from the BlackBerry Native Development plugin for Visual Studio) was to actually do it following way. I had a ‘wrapper’, that I was able to communicate with, which would trigger the Ctrl-C signal and could also spawn other child-processes, I wish to have a control over. That was OK, it helped in having some ‘domains’ of applications, although it required a lot of coding (especially the communication part and passing startup arguments for childs processes), but was working fine.

Recently, I found a simpler solution here, for scenarios, where there is no need for having groups of applications Ctrl-C should be delivered the same time. That’s correct – not the accepted answer. You can read author’s blogs post too and grab the source-code. The trick is, instead of writing another ‘wrapper’ application, it’s possible to issue the Ctrl-C directly from the manager application. We simply attach to the console of the desired program, disable exiting of the manager in case of Ctrl-C, call GenerateConsoleCtrlEvent and restore handling Ctrl-C.

Here is C# snippet from StackOverflow with few additions.

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessId);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool FreeConsole();

[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add);

// Delegate type to be used as the Handler Routine for SCCH
delegate Boolean ConsoleCtrlDelegate(CtrlTypes type);

// Enumerated type for the control messages sent to the handler routine
enum CtrlTypes : uint
{
    CTRL_C_EVENT = 0,
    CTRL_BREAK_EVENT,
    CTRL_CLOSE_EVENT,
    CTRL_LOGOFF_EVENT = 5,
    CTRL_SHUTDOWN_EVENT
}

[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);

public static void StopProgram(uint pid)
{
    // It's impossible to be attached to 2 consoles at the same time,
    // so release the current one.
    FreeConsole();

    // This does not require the console window to be visible.
    if (AttachConsole(pid))
    {
        // Disable Ctrl-C handling for our program
        SetConsoleCtrlHandler(null, true);
        GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);

        // Must wait here. If we don't and re-enable Ctrl-C
        // handling below too fast, we might terminate ourselves.
        Thread.Sleep(2000);

        FreeConsole();

        // Re-enable Ctrl-C handling or any subsequently started
        // programs will inherit the disabled state.
        SetConsoleCtrlHandler(null, false);
    }
}

This still could be improved by writing own more sophisticated handler (to avoid the sleep). More info here.