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

Mat*_*och 83 windows signals sigint

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

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

Sta*_*lav 53

我围绕这个主题做了一些研究,结果比我预期的更受欢迎.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); 
  }
}
Run Code Online (Sandbox Code Playgroud)

此外,如果AttachConsole()或者发送的信号失败,计划应急解决方案,例如睡觉然后:

if (!proc.HasExited)
{
  try
  {
    proc.Kill();
  }
  catch (InvalidOperationException e){}
}
Run Code Online (Sandbox Code Playgroud)

  • 受此启发,我创建了一个"独立"的cpp应用程序:https://gist.github.com/rdp/f51fb274d69c5c31b6be以防它有用.是的,这个方法显然可以将ctrl + c或ctrl + break发送到"any"pid. (4认同)

aro*_*101 27

我最接近解决方案的是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如果在批处理文件中运行它,则在其前面.)

  • 实际上有一个函数可以帮到你,lol,"GenerateConsoleCtrlEvent".请参阅:http://msdn.microsoft.com/en-us/library/ms683155(v = vs.85).aspx另请参阅我的回复:http://serverfault.com/a/501045/10023 (7认同)
  • 令人难以理解的是,Windows已经过时了,但是微软还没有为控制台应用程序A提供监听来自控制台应用程序B的信号的机制,这样控制台应用程序B就可以告诉控制台应用程序A干净地关闭.无论如何,除了MS抨击之外,我已经尝试过SendSignal并使用"0x00000005"获取"CreateRemoteThread失败".关于如何编写应用程序A来监听信号(任何方便的信号)以及如何发信号的任何其他想法? (3认同)
  • @ DavidI.McIntosh听起来你想要在两个进程中创建一个命名事件; 然后你就可以从另一个发出信号.查看CreateEvent的文档 (3认同)
  • +1-链接的代码使用Ctrl-Break而不是Ctrl-C,但是从表面上看(请参见GenerateConsoleCtrlEvent文档),可以将其用于Ctrl-C。 (2认同)

Sha*_*kta 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的进程groupd id直接调用GenerateConsoleCtrlEvent(),以允许帮助程序进程继续生效.这也将失败.

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

  • 谢谢你的解决方案!这是Windows API如此糟糕的另一个例子. (6认同)

joh*_*135 12

如果你的命令行中有 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)
Run Code Online (Sandbox Code Playgroud)

然后调用:

python ctrl_c.py 12345
Run Code Online (Sandbox Code Playgroud)

如果这不起作用,我建议尝试 windows-kill 项目:

  • 对不起。我知道这是相当不受欢迎的,但我真诚地想说声谢谢!:) 我已经研究这个问题好几天了,希望 ffmpeg 能够优雅地退出。与内置的“process.send_signal(signal.CTRL_C_EVENT)”配合得很好,只要我有一个控制台,但出现“pythonw”BOOM OSError。这是一个修复!谢谢! (2认同)

Ree*_*sey 7

编辑:

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

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

如果应用程序不遵守该规则,您可以调用TerminateProcess.


Kin*_*gon 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
}
Run Code Online (Sandbox Code Playgroud)


56k*_*6ka 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;
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

DWORD dwProcessId = ...;
if (signalCtrl(dwProcessId, CTRL_C_EVENT))
{
    cout << "Signal sent" << endl;
}
Run Code Online (Sandbox Code Playgroud)


小智 5

感谢jimhark 的回答和其他答案,我找到了一种在 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) 

Run Code Online (Sandbox Code Playgroud)

使事情起作用的是将其发送GenerateConsoleCtrlEvent到所需的进程组,而不是将其发送到共享调用进程的控制台的所有进程,然后AttachConsole返回到当前进程的父进程的控制台


归档时间:

查看次数:

88753 次

最近记录:

6 年,6 月 前