2009-05-01 2 views
70

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

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

+0

Я был заинтересован в посылке Ctrl-C для процессов Java услуг только для получения threaddumps. Похоже, что «jstack» может быть надежно использован для этого конкретного вопроса: https://stackoverflow.com/a/47723393/603516 – Vadzim

ответ

25

Самое близкое, что я пришел к решению, это приложение SendSignal стороннего разработчика. Автор перечисляет исходный код и исполняемый файл. Я проверил, что он работает под 64-битными окнами (работает как 32-разрядная программа, убивая еще одну 32-разрядную программу), но я не понял, как вставлять код в программу Windows (либо 32-разрядную или 64-бит).

Как это работает:

После долгих копаться в отладчике я обнаружил, что точка входа, что на самом деле делает поведение, связанное с сигналом, как Ctrl-Break является kernel32 CtrlRoutine!. Функция имела тот же прототип, что и ThreadProc, поэтому его можно использовать непосредственно с CreateRemoteThread, без необходимости вводить код. Однако это не экспортированный символ! Он находится на разных адресах (и даже имеет разные имена) в разных версиях Windows. Что делать?

Вот решение, которое я, наконец, придумал. Я устанавливаю консольный обработчик ctrl для своего приложения, а затем генерирую сигнал ctrl-break для моего приложения. Когда мой обработчик вызван, я оглядываюсь наверху стека, чтобы узнать параметры, переданные в kernel32! BaseThreadStart. Я беру первый параметр, который является желаемым начальным адресом потока, который является адресом kernel32! CtrlRoutine. Затем я возвращаюсь из своего обработчика, указывая, что я обработал сигнал, и мое приложение не должно быть прекращено. Вернемся в основной поток, я жду, пока не будет найден адрес ядра32! CtrlRoutine. Как только у меня получится, я создаю удаленный поток в целевом процессе с открытым начальным адресом. Это приводит к тому, что обработчики ctrl в целевом процессе будут оцениваться так, как если бы был нажат ctrl-break!

Приятно, что затрагивается только целевой процесс, и любой процесс (даже оконный процесс) может быть нацелен. Один недостаток заключается в том, что мое маленькое приложение не может использоваться в пакетном файле, так как оно убьет его, когда он отправит событие ctrl-break, чтобы обнаружить адрес kernel32! CtrlRoutine.

(перед ним start, если запустить его в пакетном файле.)

+1

+1 - связанный код использует Ctrl-Break, а не Ctrl-C, но на первый взгляд (смотря на документацию для GenerateConsoleCtrlEvent) может быть адаптирован для Ctrl-C. –

+5

На самом деле существует функция для этого, lol, «GenerateConsoleCtrlEvent». См. Http://msdn.microsoft.com/en-us/library/ms683155(v=vs.85).aspx См. Также мой ответ здесь: http://serverfault.com/a/501045/10023 – Triynko

+0

Github link связанные с ответом выше: https://github.com/walware/statet/tree/master/de.walware.statet.r.console.core/win32 – meawoppl

6

Edit:

Для графического интерфейса приложения, «нормальный» способ справиться с этим в развитии ОС Windows будет посылать сообщение WM_CLOSE в главное окно процесса.

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

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

+0

Я хочу сделать это в командной строке (без окон). TerminateProcess - это механизм принудительного уничтожения (аналогичный SIGKILL), поэтому он не дает прекращающемуся приложению возможности для очистки. –

+0

@Matthew Murdoch: Обновлено, чтобы включить информацию о том, как справиться с этим в консольном приложении.Вы также можете поймать другие события, включая закрытие окон или консоль, и т. Д., Используя тот же механизм. Подробные сведения см. В MSDN. –

+0

@Reed Copsey: Но я хотел бы инициировать это из отдельного процесса. Я посмотрел на GenerateConsoleCtrlEvent (ссылка на SetConsoleCtrlHandler), но это, похоже, работает только в том случае, если завершаемый процесс находится в той же «группе процессов», что и процесс, выполняющий завершение ... Любые идеи? –

15

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

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

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

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

+3

Спасибо за решение! Это еще один пример того, что Windows API является таким ужасным беспорядком. – vitaut

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 
} 
+0

и как вернуть управление? и затем я посылаю программу control-c, stopar, но я ничего не могу сделать руками. –

+0

Где вернуть контроль? – KindDragon

+0

Я думаю, что вы вызываете SetConsoleCtrlHandler (NULL, FALSE) для удаления вашего обработчика, см. Различные другие ответы. – rogerdpack

43

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

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

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

  • Начало программы с видимым окно с помощью .Net, скрытие с PInvoke, работать в течение 6 секунд, показывают с PInvoke, остановка с .Net.
  • Запуск программы без окна с использованием .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){} 
} 
+5

Это должен быть принятый ответ. – Triynko

+4

Вдохновленный этим, я сделал «автономное» приложение cpp, которое делает это: https://gist.github.com/rdp/f51fb274d69c5c31b6be, если это полезно. И да, этот метод может отправить ctrl + c или ctrl + break на «any» pid, по-видимому. – rogerdpack

+0

Отсутствует один из DLL-импорта «SetConsoleCtrlHandler», но это работает. –

1

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

1

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

«VBScript Пример

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

WshShell.Run "notepad.exe" 

WshShell.AppActivate "notepad" 

WScript.Sleep 7000 

WshShell.SendKeys "^C" 
+0

Это работает, если приложение имеет окно, вы можете AppActive (вывести на передний план). – rogerdpack

2

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

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

Я также зеркальным эти файлы только в случае, если:
32-разрядная версия: https://www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=0
64-разрядная версия: https://www.dropbox.com/s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=0

Отказ от ответственности: я не строила тех файлов s. Никакие изменения не были внесены в скомпилированные исходные файлы . Единственной протестированной платформой является 64-битная Windows 7. Рекомендуется адаптировать источник, доступный в http://www.latenighthacking.com/projects/2003/sendSignal/, и скомпилировать его самостоятельно.

2

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

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

Список весь процесс:

C:\>tasklist 

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

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

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

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

2

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

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

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