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


91

我已经(过去)写入了,在命令行启动时,处理用户输入的跨平台(Windows / Unix的)应用Ctrl-C以同样的方式组合(即彻底结束应用程序)。

在Windows上是否可以从另一个(不相关的)进程中向一个进程发送Ctrl- C/ SIGINT /等效项,以请求该进程完全终止(从而使它有机会整理资源等)?


1
我对将Ctrl-C发送到Java服务进程很感兴趣,只是为了获取线程转储。似乎jstack可以可靠地代替此特定事项使用:stackoverflow.com/a/47723393/603516
Vadzim

Answers:


31

我最接近解决方案的是SendSignal 3rd party应用程序。作者列出了源代码和可执行文件。我已经验证它可以在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如果在批处理文件中运行它,请先添加。)


2
+1-链接的代码使用Ctrl-Break而不是Ctrl-C,但是从表面上看(请参见GenerateConsoleCtrlEvent文档),可以将其用于Ctrl-C。
马修·默多克

8
实际上,有一个函数可以为您执行此操作,即“ GenerateConsoleCtrlEvent”。请参阅:msdn.microsoft.com/en-us/library/ms683155
v=vs.85).aspx

1
Github链接与上面的答案有关:github.com/walware/statet/tree/master/…– meawoppl 2013
6

3
Windows早已老化,但Microsoft尚未为控制台应用程序A提供侦听来自控制台应用程序B的信号的机制,因此控制台应用程序B可以告诉控制台应用程序A彻底关闭。无论如何,除了MS扑灭之外,我都尝试过SendSignal并得到“ CreateRemoteThread失败,出现0x00000005”。关于如何编码应用程序A侦听信号(任何方便的信号)以及如何发出信号的其他想法?
David I. McIntosh

3
@ DavidI.McIntosh听起来像您想在两个进程中创建一个命名事件。这样您就可以从另一个发出信号。查看CreateEvent的文档
arolson101

58

我已经围绕该主题进行了一些研究,结果比我预期的要受欢迎。KindDragon的答复是关键点之一。

我写了一篇有关该主题的更长的博客文章,并创建了一个有效的演示程序,该程序演示了如何使用这种系统以几种不错的方式关闭命令行应用程序。该帖子还列出了我在研究中使用的外部链接。

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

  • 使用.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){}
}

4
受此启发,我制作了一个“独立” cpp应用程序来执行此操作:gist.github.com/rdp/f51fb274d69c5c31b6be以防万一。是的,显然,该方法可以将ctrl + c或ctrl + break发送给“任何” pid。
rogerdpack

缺少一个DLL导入“ SetConsoleCtrlHandler”,但这确实可行。
Derf Skren 2015年

优秀的帖子。优秀的博客。我建议您在实验程序中完成StopProgramWithInvisibleWindowUsingPinvoke函数。我几乎以为您没有找到解决方案的想法就放弃了
Wilmer Saint

为了避免这种情况,wait()我删除了重新启用Ctrl-CSetConsoleCtrlHandler(null, false);)。我做了一些测试(多次调用,也对已经终止的进程进行了测试),但没有发现任何副作用(还可以吗?)。
2015年

发送GenerateConsoleCtrlEvent之后,应立即调用FreeConsole。在调用之前,您的应用程序将附加到另一个控制台。如果另一个控制台在关闭之前被杀死,那么您的应用程序也会被终止!
蒂莫西·詹纳斯

17

我想我在这个问题上有点迟了,但我还是会为遇到相同问题的任何人写点东西。这与我对这个问题的回答相同。

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

我设法通过带有包装器应用程序的GenerateConsoleCtrlEvent()解决了这个问题。棘手的是,文档尚不清楚如何使用以及存在的陷阱。

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

创建一个新的帮助程序“ 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()的工作方式也有些怪异,因此,如果有人知道它的更多详细信息,请分享。


6
感谢您的解决方案!这是Windows API如此糟糕的另一个例子。
vitaut 2012年

8

编辑:

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

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

如果应用程序不遵守该要求,则可以调用TerminateProcess


我想在命令行应用程序(没有Windows)中执行此操作。TerminateProcess是强制终止机制(类似于SIGKILL),因此不会给终止应用程序任何清除的机会。
马修·默多克

@Matthew Murdoch:已更新,以包括有关如何在控制台应用程序中处理此问题的信息。您也可以通过相同的机制捕获其他事件,包括Windows关闭或控制台关闭等。有关完整的详细信息,请参见MSDN。
里德·科普西

@里德·科普西(Reed Copsey):但是,我想从一个单独的过程中开始。我看过GenerateConsoleCtrlEvent(从SetConsoleCtrlHandler引用),但这似乎仅在被终止的进程与进行终止的进程在同一“进程组”中时起作用...有什么想法吗?
马修·默多克

1
Matthew:我唯一想做的就是找到该进程,找到它的父级(控制台),获取父级的主窗口句柄(控制台),然后使用SendKeys直接向其发送Ctrl + C。这将导致您的控制台进程收到CTRL_C_EVENT。不过,您还没有尝试过-不确定它的效果如何。
里德·科普西

这是一个等效于SendKeys的C ++ stackoverflow.com
questions/6838363/

7

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程序,但是我不能用他的手做任何事情。
Yaroslav L.

在哪里退回控制权?
KindDragon

我认为您调用SetConsoleCtrlHandler(NULL,FALSE)来删除您的处理程序,请参阅其他各种响应。
rogerdpack

6

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

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

2

应该使它非常清晰,因为目前还不是。 有一个SendSignal的修改和编译版本,用于发送Ctrl-C(默认情况下,它仅发送Ctrl + Break)。以下是一些二进制文件:

(2014-3-7):我使用Ctrl-C构建了32位和64位版本,称为SendSignalCtrlC.exe,您可以从以下网址 下载:https ://dl.dropboxusercontent.com/u/49065779/ sendsignalctrlc / x86 / SendSignalCtrlC.exe https://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位Windows7。建议改编http://www.latenighthacking.com/projects/2003/sendSignal/上提供的源,并自行进行编译。


2

在Java中,将JNA与Kernel32.dll库一起使用,类似于C ++解决方案。将CtrlCSender main方法作为一个进程运行,它仅获取该进程的控制台以将Ctrl + C事件发送到并生成该事件。由于它无需控制台即可单独运行,因此Ctrl + C事件无需禁用并再次启用。

CtrlCSender.java-基于Nemo1024KindDragon的答案。

给定已知的进程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();

嗯,如果我针对Powershell进程运行此进程,则该进程仍然有效。另一方面,有趣的是,在JVM上运行进程确实确实使VM崩溃了!您是否知道为什么Powershell进程无法响应该技术?
Groostav


1

从这里找到的解决方案如果您在命令行中可以使用python 3.x,非常简单。首先,保存包含以下内容的文件(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


0

我的一个朋友提出了一种解决问题的完全不同的方法,它为我工作。使用如下的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的窗口(进入前台),则此方法有效。
rogerdpack 2015年

0

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


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

0

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

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


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.