【问题标题】:Can I send a ctrl-C (SIGINT) to an application on Windows?我可以向 Windows 上的应用程序发送 ctrl-C (SIGINT) 吗?
【发布时间】:2010-10-23 05:19:06
【问题描述】:

我(过去)编写过跨平台(Windows/Unix)应用程序,当从命令行启动时,它处理用户键入的 Ctrl-C 以相同的方式组合(即干净地终止应用程序)。

是否可以在 Windows 上发送 Ctrl-C/SIGINT/equivalent 从另一个(不相关的)进程请求它干净地终止(给它有机会整理资源等)?

【问题讨论】:

标签: windows signals sigint


【解决方案1】:

编辑:

对于 GUI 应用程序,在 Windows 开发中处理此问题的“正常”方式是向进程的主窗口发送 WM_CLOSE 消息。

对于控制台应用,您需要使用SetConsoleCtrlHandler 添加CTRL_C_EVENT

如果应用程序不支持这一点,您可以致电 TerminateProcess

【讨论】:

  • 我想在命令行应用程序中执行此操作(无 Windows)。 TerminateProcess 是一种强制终止机制(类似于 SIGKILL),因此不会给终止应用程序任何清理的机会。
  • @Matthew Murdoch:已更新以包含有关如何在控制台应用程序中处理此问题的信息。您还可以通过相同的机制捕获其他事件,包括窗口关闭或控制台关闭等。有关完整详细信息,请参阅 MSDN。
  • @Reed Copsey:但我想从一个单独的过程中启动它。我已经查看了 GenerateConsoleCtrlEvent (从 SetConsoleCtrlHandler 引用),但这似乎只有在被终止的进程与执行终止的进程位于同一“进程组”中时才有效......有什么想法吗?
  • Matthew:我唯一的想法是找到进程,找到它的父进程(控制台),获取父进程的主窗口句柄(控制台)并使用 SendKeys 发送 Ctrl+ C直接给它。这应该会导致您的控制台进程收到 CTRL_C_EVENT。不过还没有尝试过 - 不确定它的效果如何。
  • 这是一个与 SendKeys stackoverflow.com/questions/6838363/… 等效的 c++,另请参阅 stackoverflow.com/questions/10407769/…,尽管显然它不能保证即使在那时也能正常工作......
【解决方案2】:

我最接近解决方案的是SendSignal 3rd 方应用程序。作者列出了源代码和可执行文件。我已经验证它可以在 64 位 Windows 下工作(作为 32 位程序运行,杀死另一个 32 位程序),但我还没有弄清楚如何将代码嵌入到 Windows 程序中(32 位或 64 位)。

它是如何工作的:

在调试器中进行了大量挖掘之后,我发现实际执行与 ctrl-break 等信号相关的行为的入口点是 kernel32!CtrlRoutine。该函数与 ThreadProc 具有相同的原型,因此可以直接与 CreateRemoteThread 一起使用,而无需注入代码。但是,这不是导出的符号!它在不同版本的 Windows 上位于不同的地址(甚至有不同的名称)。怎么办?

这是我最终想出的解决方案。我为我的应用程序安装了一个控制台 ctrl 处理程序,然后为我的应用程序生成一个 ctrl-break 信号。当我的处理程序被调用时,我回头查看堆栈顶部以找出传递给 kernel32!BaseThreadStart 的参数。我抓住第一个参数,这是线程想要的起始地址,也就是 kernel32!CtrlRoutine 的地址。然后我从我的处理程序返回,表明我已经处理了信号并且我的应用程序不应该被终止。回到主线程,我等到 kernel32!CtrlRoutine 的地址被检索到。一旦我得到它,我在目标进程中创建一个带有发现的起始地址的远程线程。这会导致目标进程中的 ctrl 处理程序被评估,就像按下 ctrl-break 一样!

好处是只有目标进程受到影响,任何进程(甚至是窗口进程)都可以成为目标。一个缺点是我的小应用程序不能在批处理文件中使用,因为它会在发送 ctrl-break 事件以发现 kernel32!CtrlRoutine 的地址时杀死它。

(如果在批处理文件中运行,请在前面加上start。)

【讨论】:

  • +1 - 链接代码使用 Ctrl-Break 而不是 Ctrl-C,但从表面上看(查看 GenerateConsoleCtrlEvent 的文档)可以适应 Ctrl-C。
  • 实际上有一个函数可以为您执行此操作,哈哈,“GenerateConsoleCtrlEvent”。见:msdn.microsoft.com/en-us/library/ms683155(v=vs.85).aspx 也见我的回复:serverfault.com/a/501045/10023
  • 上述答案相关的Github链接:github.com/walware/statet/tree/master/…
  • 令人难以置信的是,Windows 已经这么老了,但微软还没有为控制台应用程序 A 提供一种机制来监听来自控制台应用程序 B 的信号,以便控制台应用程序 B 可以告诉控制台应用程序A 干净地关闭。无论如何,除了 MS-bashing,我已经尝试过 SendSignal 并得到“CreateRemoteThread failed with 0x00000005”。关于如何编写应用程序 A 以侦听信号(任何方便的信号)以及如何发出信号的任何其他想法?
  • @DavidI.McIntosh 听起来您想在两个进程中创建一个命名事件;然后,您将能够从另一个发出信号。检查 CreateEvent 的文档
【解决方案3】:

我想我在这个问题上有点晚了,但无论如何我都会为遇到同样问题的人写一些东西。 这与我对this 问题的回答相同。

我的问题是我希望我的应用程序是一个 GUI 应用程序,但执行的进程应该在后台运行,而不附加任何交互式控制台窗口。我认为当父进程是控制台进程时,这个解决方案也应该起作用。不过,您可能必须删除“CREATE_NO_WINDOW”标志。

我设法使用带有包装应用程序的GenerateConsoleCtrlEvent() 解决了这个问题。棘手的部分是文档并没有真正清楚地说明它的确切使用方式以及它的陷阱。

我的解决方案基于here 的描述。但这并没有真正解释所有细节,并且出现错误,因此这里是有关如何使其工作的详细信息。

创建一个新的帮助应用程序“Helper.exe”。此应用程序将位于您的应用程序(父)和您希望能够关闭的子进程之间。它还将创建实际的子进程。您必须有这个“中间人”进程,否则 GenerateConsoleCtrlEvent() 将失败。

使用某种 IPC 机制从父进程与助手进程通信,助手应该关闭子进程。当助手收到此事件时,它会调用“GenerateConsoleCtrlEvent(CTRL_BREAK, 0)”,它会关闭自身和子进程。我自己为此使用了一个事件对象,当父进程想要​​取消子进程时,它会完成。

要创建您的 Helper.exe,请使用 CREATE_NO_WINDOW 和 CREATE_NEW_PROCESS_GROUP 创建它。并且在创建子进程时创建它没有标志(0),这意味着它将从其父进程派生控制台。否则将导致它忽略该事件。

每个步骤都像这样完成是非常重要的。我一直在尝试各种不同的组合,但这种组合是唯一有效的组合。您不能发送 CTRL_C 事件。它将返回成功,但会被进程忽略。 CTRL_BREAK 是唯一有效的。没关系,因为他们最终都会调用 ExitProcess()。

您也不能使用子进程 ID 的进程分组 ID 直接调用 GenerateConsoleCtrlEvent(),从而允许辅助进程继续存在。这也会失败。

我花了一整天的时间试图让这个工作。该解决方案对我有用,但如果有人要添加其他内容,请执行。我在网上找到了很多有类似问题但没有明确解决问题的人。 GenerateConsoleCtrlEvent() 的工作原理也有点奇怪,所以如果有人知道更多细节,请分享。

【讨论】:

  • 感谢您的解决方案!这是 Windows API 如此糟糕的又一个例子。
【解决方案4】:

如果您为另一个进程调用 GenerateConsoleCtrlEvent() 会以某种方式返回错误,但您可以附加到另一个控制台应用程序并将事件发送到所有子进程。

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
}

【讨论】:

  • 以及如何返回控制权?然后我发送 control-c, stopar 程序,但我不能用他的手做任何事情。
  • 在哪里返回控制权?
  • 我认为您调用 SetConsoleCtrlHandler(NULL, FALSE) 来删除您的处理程序,请参阅其他各种响应。
【解决方案5】:

我围绕这个主题做了一些研究,结果证明它比我预期的更受欢迎。 KindDragon 的回复是重点之一。

我就该主题写了一个longer blog post 并创建了一个工作演示程序,该程序演示了使用这种类型的系统以几种不错的方式关闭命令行应用程序。该帖子还列出了我在研究中使用的外部链接。

简而言之,这些演示程序执行以下操作:

  • 使用 .Net 启动带有可见窗口的程序,使用 pinvoke 隐藏,运行 6 秒,使用 pinvoke 显示,使用 .Net 停止。
  • 使用 .Net 启动一个没有窗口的程序,运行 6 秒,通过附加控制台并发出 ConsoleCtrlEvent 停止

编辑:来自@KindDragon 的修正解决方案,适用于现在对代码感兴趣的人。如果您打算在停止第一个程序后启动其他程序,则应重新启用 CTRL+C 处理,否则下一个进程将继承父进程的禁用状态,不会响应 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); 
    }
}

另外,如果AttachConsole() 或发送的信号失败,例如休眠,则计划应急解决方案:

if (!proc.HasExited)
{
    try
    {
    proc.Kill();
    }
    catch (InvalidOperationException e){}
}

【讨论】:

  • 受此启发,我制作了一个“独立”的 cpp 应用程序:gist.github.com/rdp/f51fb274d69c5c31b6be,以防它有用。是的,这个方法显然可以将 ctrl+c 或 ctrl+break 发送到“任何”pid。
  • 缺少一个 DLL 导入“SetConsoleCtrlHandler”,但这确实有效。
  • 优秀的帖子。优秀的博客。我建议在您的实验程序中完成 StopProgramWithInvisibleWindowUsingPinvoke 函数。我几乎放弃了认为你没有找到解决方案的想法
  • 为了避免 wait() 我删除了 重新启用 Ctrl-C (SetConsoleCtrlHandler(null, false);)。我做了一些测试(多次调用,也在已经终止的进程上),我没有发现任何副作用(还没有?)。
  • FreeConsole 应在发送 GenerateConsoleCtrlEvent 后立即调用。在调用它之前,您的应用程序将附加到另一个控制台。如果另一个控制台在完成关闭之前被杀死,您的应用程序也将被终止!
【解决方案6】:

我发现这一切都太复杂了,并使用SendKeysCTRL-C 击键发送到命令行窗口(即 cmd.exe 窗口)作为解决方法.

【讨论】:

    【解决方案7】:

    我的一个朋友提出了一种完全不同的解决问题的方法,它对我有用。使用如下所示的 vbscript。它启动并应用程序,让它运行 7 秒,然后使用 ctrl+c 关闭它。

    'VBScript 示例

    Set WshShell = WScript.CreateObject("WScript.Shell")
    
    WshShell.Run "notepad.exe"
    
    WshShell.AppActivate "notepad"
    
    WScript.Sleep 7000
    
    WshShell.SendKeys "^C"
    

    【讨论】:

    • 如果应用程序有一个可以 AppActive 的窗口(带到前台),则此方法有效。
    【解决方案8】:

    应该说得很清楚,因为目前还不是。 SendSignal 有一个修改和编译的版本来发送 Ctrl-C(默认它只发送 Ctrl+Break)。以下是一些二进制文件:

    (2014-3-7) : 我用 Ctrl-C 构建了 32 位和 64 位版本,它被称为 SendSignalCtrlC.exe,您可以在以下位置下载它:https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86/SendSignalCtrlC.exehttps://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe -- Juraj Michalak

    我还镜像了这些文件以防万一:
    32 位版本:https://www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0
    64位版本:https://www.dropbox.com/s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0

    免责声明:我没有构建这些文件。编译后没有做任何修改 原始文件。唯一测试的平台是 64 位 Windows 7。建议改编http://www.latenighthacking.com/projects/2003/sendSignal/ 提供的源代码并自行编译。

    【讨论】:

      【解决方案9】:

      这是我在 C++ 应用程序中使用的代码。

      积极点:

      • 从控制台应用程序工作
      • 从 Windows 服务工作
      • 无需延迟
      • 不关闭当前应用

      负面因素:

      • 主控制台丢失并创建一个新控制台(请参阅FreeConsole
      • 控制台切换会产生奇怪的结果...

      // Inspired from http://stackoverflow.com/a/15281070/1529139
      // and http://stackoverflow.com/q/40059902/1529139
      bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent)
      {
          bool success = false;
          DWORD thisConsoleId = GetCurrentProcessId();
          // Leave current console if it exists
          // (otherwise AttachConsole will return ERROR_ACCESS_DENIED)
          bool consoleDetached = (FreeConsole() != FALSE);
      
          if (AttachConsole(dwProcessId) != FALSE)
          {
              // Add a fake Ctrl-C handler for avoid instant kill is this console
              // WARNING: do not revert it or current program will be also killed
              SetConsoleCtrlHandler(nullptr, true);
              success = (GenerateConsoleCtrlEvent(dwCtrlEvent, 0) != FALSE);
              FreeConsole();
          }
      
          if (consoleDetached)
          {
              // Create a new console if previous was deleted by OS
              if (AttachConsole(thisConsoleId) == FALSE)
              {
                  int errorCode = GetLastError();
                  if (errorCode == 31) // 31=ERROR_GEN_FAILURE
                  {
                      AllocConsole();
                  }
              }
          }
          return success;
      }
      

      使用示例:

      DWORD dwProcessId = ...;
      if (signalCtrl(dwProcessId, CTRL_C_EVENT))
      {
          cout << "Signal sent" << endl;
      }
      

      【讨论】:

        【解决方案10】:

        根据进程ID,我们可以向进程发送信号以强制或优雅终止或任何其他信号。

        列出所有进程:

        C:\>tasklist
        

        终止进程:

        C:\>Taskkill /IM firefox.exe /F
        or
        C:\>Taskkill /PID 26356 /F
        

        详情:

        http://tweaks.com/windows/39559/kill-processes-from-command-prompt/

        【讨论】:

        • 大概这会发送 sigterm?
        • Taskkill 不发送 CTRL-C。检查 Windows 信号。
        【解决方案11】:

        在 Java 中,将 JNA 与 Kernel32.dll 库一起使用,类似于 C++ 解决方案。将 CtrlCSender 主方法作为 Process 运行,它只是获取进程的控制台以将 Ctrl+C 事件发送到并生成事件。由于它在没有控制台的情况下单独运行,因此不需要禁用和再次启用 Ctrl+C 事件。

        CtrlCSender.java - 基于 Nemo1024's KindDragon's 的答案。

        给定一个已知的进程 ID,这个无控制台应用程序将附加目标进程的控制台并在其上生成一个 CTRL+C 事件。

        import com.sun.jna.platform.win32.Kernel32;    
        
        public class CtrlCSender {
        
            public static void main(String args[]) {
                int processId = Integer.parseInt(args[0]);
                Kernel32.INSTANCE.AttachConsole(processId);
                Kernel32.INSTANCE.GenerateConsoleCtrlEvent(Kernel32.CTRL_C_EVENT, 0);
            }
        }
        

        主应用程序 - 将 CtrlCSender 作为单独的无控制台进程运行

        ProcessBuilder pb = new ProcessBuilder();
        pb.command("javaw", "-cp", System.getProperty("java.class.path", "."), CtrlCSender.class.getName(), processId);
        pb.redirectErrorStream();
        pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
        pb.redirectError(ProcessBuilder.Redirect.INHERIT);
        Process ctrlCProcess = pb.start();
        ctrlCProcess.waitFor();
        

        【讨论】:

        • hmm,如果我对一个 powershell 进程运行它,该进程将保持活动状态。另一方面,有趣的是,在 JVM 上运行进程内确实会导致 VM 崩溃!你知道为什么 powershell 进程不会响应这种技术吗?
        【解决方案12】:
                void SendSIGINT( HANDLE hProcess )
                {
                    DWORD pid = GetProcessId(hProcess);
                    FreeConsole();
                    if (AttachConsole(pid))
                    {
                        // Disable Ctrl-C handling for our program
                        SetConsoleCtrlHandler(NULL, true);
        
                        GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); // SIGINT
        
                        //Re-enable Ctrl-C handling or any subsequently started
                        //programs will inherit the disabled state.
                        SetConsoleCtrlHandler(NULL, false);
        
                        WaitForSingleObject(hProcess, 10000);
                    }
                }
        

        【讨论】:

          【解决方案13】:

          是的。 windows-kill 项目完全符合您的要求:

          windows-kill -SIGINT 1234
          

          【讨论】:

          • 这个项目包括准备使用的发布二进制文件在这里下载:github.com/alirdn/windows-kill/releases - 这个答案应该有更多的支持
          • 回购似乎是空的。
          • @dothebart:是的,我刚刚意识到。我更新了链接。
          【解决方案14】:
          // Send [CTRL-C] to interrupt a batch file running in a Command Prompt window, even if the Command Prompt window is not visible,
          // without bringing the Command Prompt window into focus.
          // [CTRL-C] will have an effect on the batch file, but not on the Command Prompt  window itself -- in other words,
          // [CTRL-C] will not have the same visible effect on a Command Prompt window that isn't running a batch file at the moment
          // as bringing a Command Prompt window that isn't running a batch file into focus and pressing [CTRL-C] on the keyboard.
          ulong ulProcessId = 0UL;
          // hwC = Find Command Prompt window HWND
          GetWindowThreadProcessId (hwC, (LPDWORD) &ulProcessId);
          AttachConsole ((DWORD) ulProcessId);
          SetConsoleCtrlHandler (NULL, TRUE);
          GenerateConsoleCtrlEvent (CTRL_C_EVENT, 0UL);
          SetConsoleCtrlHandler (NULL, FALSE);
          FreeConsole ();
          

          【讨论】:

            【解决方案15】:

            SIGINT可以使用windows-kill发送到程序,语法windows-kill -SIGINT PID,其中PID可以通过微软的pslist获得。

            关于捕获 SIGINT,如果您的程序是 Python 中的,那么您可以像在 this solution 中那样实现 SIGINT 处理/捕获。

            【讨论】:

              【解决方案16】:

              如果您的命令行中有可用的 python 3.x,我发现 from here 的解决方案非常简单。首先,保存一个包含内容的文件(ctrl_c.py):

              import ctypes
              import sys
              
              kernel = ctypes.windll.kernel32
              
              pid = int(sys.argv[1])
              kernel.FreeConsole()
              kernel.AttachConsole(pid)
              kernel.SetConsoleCtrlHandler(None, 1)
              kernel.GenerateConsoleCtrlEvent(0, 0)
              sys.exit(0)
              

              然后调用:

              python ctrl_c.py 12345
              

              如果这不起作用,我建议尝试 windows-kill 项目:https://github.com/alirdn/windows-kill

              【讨论】:

              • 对不起。我知道这是相当不受欢迎的,但我真诚地只想说声谢谢! :) 我已经研究了好几天了,希望 ffmpeg 能够优雅地退出。使用内置process.send_signal(signal.CTRL_C_EVENT) 非常好,只要我有一个控制台,但在pythonw BOOM OSError 上。这是一个修复! ?谢谢!
              【解决方案17】:

              感谢jimhark's answer 和此处的其他答案,我找到了在 PowerShell 中执行此操作的方法:

              $ProcessID = 1234
              $MemberDefinition = '
                  [DllImport("kernel32.dll")]public static extern bool FreeConsole();
                  [DllImport("kernel32.dll")]public static extern bool AttachConsole(uint p);
                  [DllImport("kernel32.dll")]public static extern bool GenerateConsoleCtrlEvent(uint e, uint p);
                  public static void SendCtrlC(uint p) {
                      FreeConsole();
                      if (AttachConsole(p)) {
                          GenerateConsoleCtrlEvent(0, p);
                          FreeConsole();
                      }
                      AttachConsole(uint.MaxValue);
                  }'
              Add-Type -Name 'dummyName' -Namespace 'dummyNamespace' -MemberDefinition $MemberDefinition
              [dummyNamespace.dummyName]::SendCtrlC($ProcessID) 
              
              

              成功的原因是将GenerateConsoleCtrlEvent 发送到所需的进程组,而不是将其发送到所有共享调用进程控制台的进程,然后将AttachConsole 发送回当前进程的父级控制台

              【讨论】:

              • 此脚本中的结尾 } 需要删除才能正常工作。当我尝试修复时,“建议的编辑队列已满”。
              猜你喜欢
              • 2010-11-08
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多