我可以向Windows上的应用程序发送ctrl-C(SIGINT)吗?

时间:2009-05-01 20:18:51

标签: windows signals sigint

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

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

17 个答案:

答案 0 :(得分:52)

我围绕这个主题做了一些研究,结果比我预期的更受欢迎。 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){}
}

答案 1 :(得分:26)

我最接近解决方案的是SendSignal第三方应用。作者列出了源代码和可执行文件。我已经验证它在64位窗口下工作(作为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为前缀。)

答案 2 :(得分:17)

我想我在这个问题上有点迟了但是无论如何我会为有同样问题的人写点什么。 这与我对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的进程groupd id直接调用GenerateConsoleCtrlEvent(),从而允许帮助程序进程继续生效。这也会失败。

我花了一整天努力让这个工作。这个解决方案适合我,但如果有人有任何其他要添加,请做。我在网上找到了许多有类似问题但没有明确解决问题的人。 GenerateConsoleCtrlEvent()如何工作也有点奇怪,所以如果有人知道它的更多细节请分享。

答案 3 :(得分:6)

如果您为另一个进程调用它,某种方式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
}

答案 4 :(得分:6)

编辑:

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

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

如果申请不符合,则可以致电TerminateProcess

答案 5 :(得分:5)

以下是我在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;
}

答案 6 :(得分:4)

        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);
            }
        }

答案 7 :(得分:3)

它应该是清晰的,因为目前它不是。 SendSignal的修改和编译版本发送Ctrl-C (默认情况下它只发送Ctrl + Break)。这是一些二进制文件:

  

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

我也反映了这些文件以防万一:
32位版本:https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe
64位版本:https://www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0

免责声明:我没有构建这些文件。没有修改编译 原始文件。测试的唯一平台是64位Windows 7.建议调整https://www.dropbox.com/s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0中可用的源并自行编译。

答案 8 :(得分:3)

在Java中,将JNA与Kernel32.dll库一起使用,类似于C ++解决方案。将CtrlCSender主方法作为进程运行,只需获取进程的控制台即可将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();

答案 9 :(得分:1)

我的一位朋友建议采用完全不同的方式解决问题,这对我有用。使用如下的vbscript。它启动并应用,让它运行7秒并使用 ctrl + c 关闭它。

'VBScript示例

Set WshShell = WScript.CreateObject("WScript.Shell")

WshShell.Run "notepad.exe"

WshShell.AppActivate "notepad"

WScript.Sleep 7000

WshShell.SendKeys "^C"

答案 10 :(得分:1)

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

答案 11 :(得分:1)

根据进程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/

答案 12 :(得分:1)

是的。 windows-kill项目完全可以满足您的需求:

windows-kill -SIGINT 1234

答案 13 :(得分:1)

如果命令行中提供了python 3.x,我发现的from here解决方案非常简单。首先,保存包含以下内容的文件(ctrl_c.py):

interface ChildProps {
  name: string;
  onChange: (newName: string) => void;
  onDelete: () => void;
}

const ChildComponent: React.FC<ChildProps> = React.memo(
  ({ name, onChange, onDelete }) => {
    return (
      <div>
        <input
          value={name}
          onChange={(event) => onChange(event.target.value)}/>
      </div>);});

function ParentComponent() {
  const [names, setNames] = React.useState(["First", "Second"]);

  const onNameChange = (index: number, newName: string) => {
    const mutatedNames = [...names];
    mutatedNames[index] = newName;
    setNames(mutatedNames);
    console.log(mutatedNames);
  };
  return (
    <div>
      {names.map((name, index) => (
        <ChildComponent
          key={index}
          name={name}
          onChange={() => onNameChange(index, name)}
          onDelete={() => {
            setNames((prev) => prev.filter((_, i) => i !== index));
          }}/>))}</div>);}

然后致电:

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)

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

答案 14 :(得分:1)

感谢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发送到所需的进程组,而不是将all processes that share the console of the calling processAttachConsole发送回the console of the parent of the current process

答案 15 :(得分:0)

// 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 ();

答案 16 :(得分:0)

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

关于捕获SIGINT,如果您的程序使用Python,则可以像在this solution中一样实现SIGINT处理/捕获。