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

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

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

12 ответов

Решение

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

Я провел некоторое исследование по этой теме, которое оказалось более популярным, чем я ожидал. Ответ 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();

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

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

Моя проблема заключалась в том, что я хотел бы, чтобы мое приложение было приложением с графическим интерфейсом, но выполняемые процессы должны запускаться в фоновом режиме без какого-либо окна интерактивной консоли. Я думаю, что это решение также должно работать, когда родительский процесс является консольным процессом. Возможно, вам придется удалить флаг "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() в конце.

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

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

Решение, которое я нашел здесь, довольно простое, если в вашей командной строке доступен 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

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

Вот код, который я использую в своем приложении C++.

Положительные моменты:

  • Работает из консольного приложения
  • Работает из службы Windows
  • Никаких задержек не требуется
  • Не закрывает текущее приложение

Отрицательные моменты:

  • Основная консоль потеряна и создана новая (см. FreeConsole)
  • Переключение консоли дает странные результаты...

// Inspired from http://stackru.com/a/15281070/1529139
// and http://stackru.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;
}

Редактировать:

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

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

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

Благодаря ответу 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) }

Что заставляло все работать, так это отправка GenerateConsoleCtrlEvent в желаемую группу процессов вместо all processes that share the console of the calling processи AttachConsole обратно в the console of the parent of the current process.

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

Это должно быть сделано кристально ясно, потому что на данный момент это не так. Существует измененная и скомпилированная версия 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/ и скомпилировать его самостоятельно.

Да. windows-kill Проект делает именно то, что вы хотите:

windows-kill -SIGINT 1234

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

CtrlCSender.java - Основано на ответах Nemo1024 и KindDragon.

При известном идентификаторе процесса это совместимое приложение подключит консоль целевого процесса и сгенерирует на нем событие 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();

На основе идентификатора процесса мы можем отправить сигнал процессу, чтобы принудительно или грациозно завершить его, или любой другой сигнал.

Перечислите весь процесс:

C:\>tasklist

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

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

Подробности:

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

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

Пример VBScript

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

WshShell.Run "notepad.exe"

WshShell.AppActivate "notepad"

WScript.Sleep 7000

WshShell.SendKeys "^C"

SIGINT можно отправить программе с помощью windows-kill, по синтаксису windows-kill -SIGINT PID, где PIDможно получить через Microsoft's pslist.

Что касается перехвата SIGINT, если ваша программа написана на Python, вы можете реализовать обработку / перехват SIGINT, как в этом решении.

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

// 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 ();
Другие вопросы по тегам