Можно ли отправить ctrl-C (SIGINT) в приложение в Windows?
У меня есть (в прошлом) письменные кросс-платформенные (Windows/Unix) приложения, которые при запуске из командной строки обрабатывали пользовательскую типизированную комбинацию Ctrl - C таким же образом (то есть для завершения приложение чисто).
Возможно ли, что Windows отправит Ctrl - C/SIGINT/эквивалент процессу из другого (несвязанного) процесса, чтобы запросить его прекращение (давая ему возможность убирать ресурсы и т.д.)?
Ответы
Ответ 1
Самое близкое, что я пришел к решению, - это стороннее приложение 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
если он запущен в пакетном файле.)
Ответ 2
Я провел некоторое исследование по этой теме, которое оказалось более популярным, чем я ожидал. Ответ 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);
}
}
Кроме того, спланируйте решение на случай непредвиденных обстоятельств, если AttachConsole()
или отправленный сигнал не будут работать, например, спящий режим, а затем:
if (!proc.HasExited)
{
try
{
proc.Kill();
}
catch (InvalidOperationException e){}
}
Ответ 3
Я думаю, я немного опаздываю на этот вопрос, но я напишу что-нибудь в любом случае для тех, кто имеет ту же проблему.
Это тот же ответ, что и я дал этому вопросу.
Моя проблема заключалась в том, что я хотел бы, чтобы мое приложение было графическим приложением, но выполняемые процессы должны выполняться в фоновом режиме без подключения интерактивного консольного окна. Я думаю, что это решение также должно работать, когда родительский процесс является консольным процессом. Возможно, вам придется удалить флаг 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() с идентификатором process groupd идентификатора дочернего процесса, который напрямую позволяет вспомогательному процессу продолжать жить. Это также не удастся.
Я потратил целый день, пытаясь заставить это работать. Это решение работает для меня, но если у кого-то есть что-то еще, добавьте, пожалуйста. Я пошел по сети, найдя много людей с подобными проблемами, но без определенного решения проблемы. Как работает GenerateConsoleCtrlEvent(), это также немного странно, поэтому, если кто-то знает более подробные сведения об этом, делитесь им.
Ответ 4
Edit:
Для приложения GUI "нормальный" способ справиться с этим при разработке Windows - отправить сообщение WM_CLOSE в главное окно процесса.
Для консольного приложения вам нужно использовать SetConsoleCtrlHandler, чтобы добавить CTRL_C_EVENT
.
Если приложение не соблюдает это, вы можете вызвать TerminateProcess.
Ответ 5
Как-то 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
}
Ответ 6
Вот код, который я использую в своем приложении на С++.
Положительные точки:
- Работает с консольным приложением
- Работает из службы 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;
}
Ответ 7
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);
}
}
Ответ 8
Это должно быть ясно, потому что в данный момент это не так.
Существует измененная и скомпилированная версия 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/, и скомпилировать его самостоятельно.
Ответ 9
В Java, используя JNA с библиотекой Kernel32.dll, похож на С++-решение. Запускает основной метод 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();
Ответ 10
На основе идентификатора процесса мы можем отправить сигнал для обработки, чтобы прекратить принудительно или изящно или любой другой сигнал.
Список всех процессов:
C:\>tasklist
Чтобы убить процесс:
C:\>Taskkill /IM firefox.exe /F
or
C:\>Taskkill /PID 26356 /F
Подробнее:
http://tweaks.com/windows/39559/kill-processes-from-command-prompt/
Ответ 11
Я нашел все это слишком сложным и использовал SendKeys для отправки нажатия клавиши CTRL - C в окно командной строки (например, окно cmd.exe) в качестве обходного пути.
Ответ 12
Друг мой предложил совершенно другой способ решения проблемы, и это сработало для меня. Используйте vbscript, как показано ниже. Он запускается и запускается, запускается в течение 7 секунд и закрывает его с помощью ctrl + c.
'Пример VBScript
Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.Run "notepad.exe"
WshShell.AppActivate "notepad"
WScript.Sleep 7000
WshShell.SendKeys "^C"