Can I send a ctrl-C (SIGINT) to an application on Windows?

前端 未结 17 2352
甜味超标
甜味超标 2020-11-22 09:15

I have (in the past) written cross-platform (Windows/Unix) applications which, when started from the command line, handled a user-typed Ctrl-C combinat

相关标签:
17条回答
  • 2020-11-22 09:50

    I found all this too complicated and used SendKeys to send a CTRL-C keystroke to the command line window (i.e. cmd.exe window) as a workaround.

    0 讨论(0)
  • SIGINT can be send to program using windows-kill, by syntax windows-kill -SIGINT PID, where PID can be obtained by Microsoft's pslist.

    Regarding catching SIGINTs, if your program is in Python then you can implement SIGINT processing/catching like in this solution.

    0 讨论(0)
  • 2020-11-22 09:56

    The closest that I've come to a solution is the SendSignal 3rd party app. The author lists source code and an executable. I've verified that it works under 64-bit windows (running as a 32-bit program, killing another 32-bit program), but I've not figured out how to embed the code into a windows program (either 32-bit or 64-bit).

    How it works:

    After much digging around in the debugger I discovered that the entry point that actually does the behavior associated with a signal like ctrl-break is kernel32!CtrlRoutine. The function had the same prototype as ThreadProc, so it can be used with CreateRemoteThread directly, without having to inject code. However, that's not an exported symbol! It's at different addresses (and even has different names) on different versions of Windows. What to do?

    Here is the solution I finally came up with. I install a console ctrl handler for my app, then generate a ctrl-break signal for my app. When my handler gets called, I look back at the top of the stack to find out the parameters passed to kernel32!BaseThreadStart. I grab the first param, which is the desired start address of the thread, which is the address of kernel32!CtrlRoutine. Then I return from my handler, indicating that I have handled the signal and my app should not be terminated. Back in the main thread, I wait until the address of kernel32!CtrlRoutine has been retrieved. Once I've got it, I create a remote thread in the target process with the discovered start address. This causes the ctrl handlers in the target process to be evaluated as if ctrl-break had been pressed!

    The nice thing is that only the target process is affected, and any process (even a windowed process) can be targeted. One downside is that my little app can't be used in a batch file, since it will kill it when it sends the ctrl-break event in order to discover the address of kernel32!CtrlRoutine.

    (Precede it with start if running it in a batch file.)

    0 讨论(0)
  • 2020-11-22 09:56

    I have done some research around this topic, which turned out to be more popular than I anticipated. KindDragon's reply was one of the pivotal points.

    I wrote a longer blog post on the topic and created a working demo program, which demonstrates using this type of system to close a command line application in a couple of nice fashions. That post also lists external links that I used in my research.

    In short, those demo programs do the following:

    • Start a program with a visible window using .Net, hide with pinvoke, run for 6 seconds, show with pinvoke, stop with .Net.
    • Start a program without a window using .Net, run for 6 seconds, stop by attaching console and issuing ConsoleCtrlEvent

    Edit: The amended solution from KindDragon for those who are interested in the code here and now. If you plan to start other programs after stopping the first one, you should re-enable Ctrl-C handling, otherwise the next process will inherit the parent's disabled state and will not respond to Ctrl-C.

    [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 HandlerRoutine, bool Add);
    
    delegate bool ConsoleCtrlDelegate(CtrlTypes CtrlType);
    
    // 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 void StopProgram(Process proc)
    {
      //This does not require the console window to be visible.
      if (AttachConsole((uint)proc.Id))
      {
        // Disable Ctrl-C handling for our program
        SetConsoleCtrlHandler(null, true); 
        GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);
    
        //Moved this command up on suggestion from Timothy Jannace (see comments below)
        FreeConsole();
    
        // Must wait here. If we don't and re-enable Ctrl-C
        // handling below too fast, we might terminate ourselves.
        proc.WaitForExit(2000);
    
        //Re-enable Ctrl-C handling or any subsequently started
        //programs will inherit the disabled state.
        SetConsoleCtrlHandler(null, false); 
      }
    }
    

    Also, plan for a contingency solution if AttachConsole() or the sent signal should fail, for instance sleeping then this:

    if (!proc.HasExited)
    {
      try
      {
        proc.Kill();
      }
      catch (InvalidOperationException e){}
    }
    
    0 讨论(0)
  • 2020-11-22 09:59

    Somehow GenerateConsoleCtrlEvent() return error if you call it for another process, but you can attach to another console application and send event to all child processes.

    void SendControlC(int pid)
    {
        AttachConsole(pid); // attach to process console
        SetConsoleCtrlHandler(NULL, TRUE); // disable Control+C handling for our app
        GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); // generate Control+C event
    }
    
    0 讨论(0)
  • 2020-11-22 10:00

    Yes. The windows-kill project does exactly what you want:

    windows-kill -SIGINT 1234
    
    0 讨论(0)
提交回复
热议问题