Как определить, когда подпроцесс запрашивает ввод в Windows
у меня есть подпроцесс, который либо завершает работу с кодом возврата, либо спрашивает что-то и ждет ввода пользователя.
Я хотел бы определить, когда процесс задает вопрос и немедленно завершает работу. Тот факт, что процесс задает вопрос или нет, достаточно для меня, чтобы решить состояние системы.
проблема в том, что я не могу прочитать вопрос, потому что дочерний процесс, вероятно, не очищает стандартный вывод. Поэтому я не могу полагаться на разбор subprocess.Popen().stdout
: при попытке чтобы прочитать его, ну, он блокирует, потому что ввод читается первым.
вот так
# ask.py, just asks something without printing anything if a condition is met
# here, we'll say that the condition is always met
input()
конечно, фактический подпроцесс является сторонним двоичным файлом, и я не могу легко изменить его, чтобы добавить необходимые вызовы flush, которые его решат.
Я также мог бы попробовать эквивалент Windows unbuffer
(что такое эквивалент программы unbuffer в Windows?), которая называется winpty
, который (возможно) позволил бы мне обнаружить выход и решить моя текущая проблема, но я хотел бы сохранить ее простой, и я хотел бы решить стандарт вход вопрос первый...
я попробовал... ну, много вещей, которые не работают, включая попытку передать поддельный файл как , который не работает, потому что subprocess
принимает fileno
из файла, и мы не можем кормить его мусором...
p = subprocess.Popen(["python","ask.py"],...)
используя communicate
со строкой не работает, потому что вы не можете контролировать, когда строка читается скармливать подпроцесс (возможно, через системный канал).
эти вопросы были многообещающими, но либо полагались на стандартный вывод, либо применялись только к Linux
- обнаружение, когда дочерний процесс ждет ввода
- как я могу знать, ждет ли мой подпроцесс моего ввода ?(in python3)
то, что я сейчас делаю, - это запуск процесса с тайм-аутом, и если тайм-аут достигнут, я тогда решите, что программа заблокирована. Но это стоит времени ожидания. Если бы я мог принять решение, как только ... --5--> читается подпроцессом, что было бы лучше.
Я хотел бы знать, есть ли собственное решение python (возможно, используя ctypes
и расширения windows) для обнаружения чтения из stdin. Но родное решение, которое не использует Python, но не является проприетарным языком Microsoft, может сделать это.
2 ответов
если мы не хотим позволять дочернему процессу обрабатывать пользовательский ввод, но просто убить его в этом случае, решение может быть следующим:
- запустить дочерний процесс с перенаправленными stdin в трубу.
- конец сервера трубы мы создаем в асинхронном режиме и трубе основного набора буфера в 0 размере
- перед запуском child-напишите 1 байт в этот канал.
- потому что буфер трубы 0 размер-операция не завершена, пока другой сторона не читала это байт
- после того, как мы напишем этот 1 байт и операция выполняется (в ожидании) - запустить дочерний процесс.
- наконец-то начать ждать, что завершить сначала: написать операцию или дочерний процесс ?
- если написать complete first-это значит, что дочерний процесс начинается с чтения от stdin - значит убить его в этот момент
одна возможная реализация на c++:
struct ReadWriteContext : public OVERLAPPED
{
enum OpType : char { e_write, e_read } _op;
BOOLEAN _bCompleted;
ReadWriteContext(OpType op) : _op(op), _bCompleted(false)
{
}
};
VOID WINAPI OnReadWrite(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, OVERLAPPED* lpOverlapped)
{
static_cast<ReadWriteContext*>(lpOverlapped)->_bCompleted = TRUE;
DbgPrint("%u:%x %p\n", static_cast<ReadWriteContext*>(lpOverlapped)->_op, dwErrorCode, dwNumberOfBytesTransfered);
}
void nul(PCWSTR lpApplicationName)
{
ReadWriteContext wc(ReadWriteContext::e_write), rc(ReadWriteContext::e_read);
static const WCHAR pipename[] = L"\\?\pipe\{221B9EC9-85E6-4b64-9B70-249026EFAEAF}";
if (HANDLE hPipe = CreateNamedPipeW(pipename, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, 1, 0, 0, 0, 0))
{
static SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };
PROCESS_INFORMATION pi;
STARTUPINFOW si = { sizeof(si)};
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = CreateFileW(pipename, FILE_GENERIC_READ|FILE_GENERIC_WRITE, 0, &sa, OPEN_EXISTING, 0, 0);
if (INVALID_HANDLE_VALUE != si.hStdInput)
{
char buf[256];
if (WriteFileEx(hPipe, "\n", 1, &wc, OnReadWrite))
{
si.hStdError = si.hStdOutput = si.hStdInput;
if (CreateProcessW(lpApplicationName, 0, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &si, &pi))
{
CloseHandle(pi.hThread);
BOOLEAN bQuit = true;
goto __read;
do
{
bQuit = true;
switch (WaitForSingleObjectEx(pi.hProcess, INFINITE, TRUE))
{
case WAIT_OBJECT_0:
DbgPrint("child terminated\n");
break;
case WAIT_IO_COMPLETION:
if (wc._bCompleted)
{
DbgPrint("child read from hStdInput!\n");
TerminateProcess(pi.hProcess, 0);
}
else if (rc._bCompleted)
{
__read:
rc._bCompleted = false;
if (ReadFileEx(hPipe, buf, sizeof(buf), &rc, OnReadWrite))
{
bQuit = false;
}
}
break;
default:
__debugbreak();
}
} while (!bQuit);
CloseHandle(pi.hProcess);
}
}
CloseHandle(si.hStdInput);
// let execute pending apc
SleepEx(0, TRUE);
}
CloseHandle(hPipe);
}
}
другой вариант завершения события использования кода, вместо БТР. однако это не влияет на конечный результат. этот вариант кода дает абсолютно тот же результат, что и первый:
void nul(PCWSTR lpApplicationName)
{
OVERLAPPED ovw = {}, ovr = {};
if (ovr.hEvent = CreateEvent(0, 0, 0, 0))
{
if (ovw.hEvent = CreateEvent(0, 0, 0, 0))
{
static const WCHAR pipename[] = L"\\?\pipe\{221B9EC9-85E6-4b64-9B70-249026EFAEAF}";
if (HANDLE hPipe = CreateNamedPipeW(pipename, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, 1, 0, 0, 0, 0))
{
static SECURITY_ATTRIBUTES sa = { sizeof(sa), 0, TRUE };
PROCESS_INFORMATION pi;
STARTUPINFOW si = { sizeof(si)};
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = CreateFileW(pipename, FILE_GENERIC_READ|FILE_GENERIC_WRITE, 0, &sa, OPEN_EXISTING, 0, 0);
if (INVALID_HANDLE_VALUE != si.hStdInput)
{
char buf[256];
if (!WriteFile(hPipe, "\n", 1, 0, &ovw) && GetLastError() == ERROR_IO_PENDING)
{
si.hStdError = si.hStdOutput = si.hStdInput;
if (CreateProcessW(lpApplicationName, 0, 0, 0, TRUE, CREATE_NO_WINDOW, 0, 0, &si, &pi))
{
CloseHandle(pi.hThread);
BOOLEAN bQuit = true;
HANDLE h[] = { ovr.hEvent, ovw.hEvent, pi.hProcess };
goto __read;
do
{
bQuit = true;
switch (WaitForMultipleObjects(3, h, false, INFINITE))
{
case WAIT_OBJECT_0 + 0://read completed
__read:
if (ReadFile(hPipe, buf, sizeof(buf), 0, &ovr) || GetLastError() == ERROR_IO_PENDING)
{
bQuit = false;
}
break;
case WAIT_OBJECT_0 + 1://write completed
DbgPrint("child read from hStdInput!\n");
TerminateProcess(pi.hProcess, 0);
break;
case WAIT_OBJECT_0 + 2://process terminated
DbgPrint("child terminated\n");
break;
default:
__debugbreak();
}
} while (!bQuit);
CloseHandle(pi.hProcess);
}
}
CloseHandle(si.hStdInput);
}
CloseHandle(hPipe);
// all pending operation completed here.
}
CloseHandle(ovw.hEvent);
}
CloseHandle(ovr.hEvent);
}
}
моя идея узнать, читает ли подпроцесс пользовательский ввод, заключается в том, чтобы (ab)использовать тот факт, что файловые объекты имеют статус: если процесс читает данные из своего stdin, мы должны быть в состоянии обнаружить изменение состояния stdin.
процедура выглядит следующим образом:
- создайте временный файл, который будет использоваться в качестве stdin подпроцесса
- записать некоторые данные в файл
- запустить процесс
- подождите немного, пока процесс будет читать данные (или нет), затем используйте
tell()
метод, чтобы узнать, было ли что-нибудь прочитано из файла
этот код:
import os
import time
import tempfile
import subprocess
# create a file that we can use as the stdin for the subprocess
with tempfile.TemporaryFile() as proc_stdin:
# write some data to the file for the subprocess to read
proc_stdin.write(b'whatever\r\n')
proc_stdin.seek(0)
# start the thing
cmd = ["python","ask.py"]
proc = subprocess.Popen(cmd, stdin=proc_stdin, stdout=subprocess.PIPE)
# wait for it to start up and do its thing
time.sleep(1)
# now check if the subprocess read any data from the file
if proc_stdin.tell() == 0:
print("it didn't take input")
else:
print("it took input")
В идеале временный файл может быть заменен каким-то каналом или чем-то, что не записывает данные на диск, но, к сожалению, я не мог найти способ заставить его работать без реального файла на диске.