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




signals (10)

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

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


最接近的解决方案是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 。)


我的一位朋友提出了解决问题的完全不同的方式,它对我有用。 使用下面的vbscript。 它启动并运行,让它运行7秒钟并使用ctrl + c关闭它。

'VBScript示例

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

WshShell.Run "notepad.exe"

WshShell.AppActivate "notepad"

WScript.Sleep 7000

WshShell.SendKeys "^C"

我想我在这个问题上有点晚了,但是我会为任何有同样问题的人写些东西。 这与我给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的进程组标识直接调用GenerateConsoleCtrlEvent(),以允许辅助进程继续生存。 这也会失败。

我花了整整一天的时间努力做到这一点。 这个解决方案适用于我,但如果任何人有任何其他补充,请做。 我遍布网络发现了许多有类似问题的人,但没有明确的解决方案。 GenerateConsoleCtrlEvent()的工作原理也有点奇怪,所以如果有人知道更多细节,请分享。



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

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

积极点:

  • 从控制台应用中运行
  • 从Windows服务工作
  • 不需要延迟
  • 不关闭当前的应用程序

负面点:

  • 主控制台丢失,并创建一个新控制台(请参阅FreeConsole
  • 控制台切换会产生奇怪的结果...
// Inspired from http://.com/a/15281070/1529139
// and http://.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;
}

编辑:

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

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

如果应用程序不尊重这一点,则可以调用TerminateProcess


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


它应该变得清晰,因为目前它不是。 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/SendSignalCtrlC.exe ://dl.dropboxusercontent.com/u/49065779/ 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=0https://www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0
64位版本: https://www.dropbox.com/s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0 : https://www.dropbox.com/s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0

免责声明:我没有建立这些文件。 没有对已编译的原始文件进行修改。 唯一测试的平台是64位Windows 7.建议您调整http://www.latenighthacking.com/projects/2003/sendSignal/的源代码并自行编译。


如果您为其他进程调用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
}




sigint