Могу ли я отправить ctrl-C (SIGINT) в приложение в Windows?

У меня есть (в прошлом) написанные кросс-платформенные (Windows/Unix) приложения, которые при запуске из командной строки обрабатывались пользователем Ctrl -C комбинация таким же образом (т. е. завершить приложение чисто).

возможно ли в Windows отправить Ctrl -C/SIGINT / эквивалентно процессу из другого (несвязанного) процесса, чтобы запросить его завершение чисто (давая ему возможность привести в порядок ресурсов и т. д.)?

12 ответов


самое близкое, что я пришел к решению-это SendSignal 3-й партии приложение. Автор перечисляет исходный код и исполняемый файл. Я проверил, что он работает под 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 при запуске в пакетном файле.)


Я сделал некоторые исследования по этой теме, которая оказалась более популярной, чем я ожидал. Ответ KindDragon был одним из поворотных пунктов.

Я написал более длинный пост в блоге по теме и создана рабочая демо-программа, которая демонстрирует использование этого типа системы для закрытия приложения командной строки в пару приятных мод. В этом посте также перечислены внешние ссылки, которые я использовал в своем исследовании.

короче говоря, эти демо-программы делают следующий:

  • запустить программу с видимое окно, используя .Чистая, скрывает при использовании PInvoke, выполнено за 6 секунд, показать при использовании PInvoke, остановка .Нет.
  • запустите программу без окна с помощью .Net, запустите в течение 6 секунд, остановитесь, подключив консоль и выдав ConsoleCtrlEvent

Edit: измененное решение от 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();

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

    // Must wait here. If we don't and re-enable Ctrl-C
    // handling below too fast, we might terminate ourselves.
    proc.WaitForExit(2000);

    FreeConsole();

    //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){}
}

думаю, я немного опоздал на этот вопрос, но я все равно напишу что-нибудь для тех, у кого такая же проблема. Это тот же ответ, который я дал этой вопрос.

моя проблема заключалась в том, что я хотел бы, чтобы мое приложение было приложением GUI, но выполняемые процессы должны выполняться в фоновом режиме без какого-либо интерактивного окна консоли. Я думаю, что это решение также должно работать, когда родительский процесс является консольным процессом. Возможно, вам придется удалить Однако флаг "CREATE_NO_WINDOW".

мне удалось решить эту проблему с помощью GenerateConsoleCtrlEvent() С приложением фантик. Хитрая часть заключается в том, что документация не совсем ясна о том, как именно ее можно использовать и подводные камни с ней.

мое решение основано на том, что описано здесь. Но это на самом деле не объясняло все детали и с ошибкой, так что вот подробности о том, как заставить его работать.

создать новый вспомогательное приложение "Помощник.исполняемый." Это приложение будет сидеть между вашим приложением (родителем) и дочерним процессом, который вы хотите закрыть. Это также создаст фактический дочерний процесс. У вас должен быть этот процесс "среднего человека", или GenerateConsoleCtrlEvent() потерпит неудачу.

используйте какой-то механизм IPC для передачи от родителя вспомогательному процессу, что помощник должен закрыть дочерний процесс. Когда помощник получает это событие, он вызывает " GenerateConsoleCtrlEvent(CTRL_BREAK, 0)", который закрывает себя и дочерний процесс. Я использовал объект события для этого сам, который родитель завершает, когда он хочет отменить дочерний процесс.

создать ваш помощник.exe создайте его с помощью CREATE_NO_WINDOW и CREATE_NEW_PROCESS_GROUP. И при создании дочернего процесса создайте его без флагов (0), что означает, что он будет наследовать консоль от своего родителя. Неспособность сделать это приведет к игнорированию события.

очень важно, чтобы каждый шаг был сделан вроде этого. Я пробовал разные комбинации, но эта комбинация-единственная, которая работает. Вы не можете отправить событие CTRL_C. Он вернет успех, но будет проигнорирован процессом. CTRL_BREAK-единственный, который работает. Не имеет значения, так как они оба вызова ExitProcess() в конце.

вы также не можете вызвать GenerateConsoleCtrlEvent() с идентификатором процесса groupd идентификатора дочернего процесса, непосредственно позволяя вспомогательному процессу продолжать жить. Эта воля и потерпеть неудачу.

Я потратил целый день, пытаясь получить эту работу. Это решение работает для меня, но если у кого есть что-нибудь добавить, пожалуйста. Я обошел всю сеть, найдя много людей с похожими проблемами, но не нашел определенного решения проблемы. Как GenerateConsoleCtrlEvent () работает также немного странно, поэтому, если кто-нибудь знает больше деталей об этом, пожалуйста, поделитесь.


изменить:

для приложения GUI "обычным" способом обработки этого в разработке Windows было бы отправить сообщение WM_CLOSE в Главное окно процесса.

для консольного приложения вам нужно использовать SetConsoleCtrlHandler добавить CTRL_C_EVENT.

Если приложение не соблюдает это, вы можете позвонить TerminateProcess.


как-то 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
}

Это должно быть кристально ясно, потому что на данный момент это не так. существует измененная и скомпилированная версия SendSignal для отправки Ctrl-C (по умолчанию он отправляет только Ctrl+Break). Вот некоторые двоичные файлы:

(2014-3-7): я построил как 32-разрядную, так и 64-разрядную версию с Ctrl-C, она называется SendSignalCtrlC.exe, и вы можете скачать его по адресу:https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86/SendSignalCtrlC.exe https://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe -- Юрай Михалак

Я также отразил эти файлы на всякий случай:
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/ и скомпилируйте его самостоятельно.


вот код, который я использую в своем приложении 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;
}

        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:\>tasklist

убить процесс:

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

детали:

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


в Java, используя Юна в библиотеке kernel32.библиотека dll, аналогичная решению на C++. Запускает метод CtrlCSender main как процесс, который просто получает консоль процесса для отправки события Ctrl+C и генерирует событие. Поскольку он работает отдельно без консоли, событие Ctrl+C не нужно отключать и снова включать.

CtrlCSender.java - на основании Nemo1024 это и KindDragon это!--8--> ответы.

дано известно идентификатор процесса, это приложение consoless присоединит консоль целевого процесса и сгенерирует событие 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 как отдельный процесс consoless

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

мой друг предложил совершенно другой способ решения проблемы, и это сработало для меня. Используйте vbscript, как показано ниже. Он запускается и приложение, пусть он работает в течение 7 секунд и закройте его с помощью ctrl+c.

' Пример VBScript

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

WshShell.Run "notepad.exe"

WshShell.AppActivate "notepad"

WScript.Sleep 7000

WshShell.SendKeys "^C"

Я нашел все это слишком сложно и использовать sendkeys будет отправить CTRL-C нажатие клавиши в окне командной строки (т. е. cmd.exe window) В качестве обходного пути.