我有一个子进程要么退出并返回代码,要么询问一些事情并等待用户输入。
我想检测进程何时提出问题并立即退出。进程是否提出问题这一事实足以让我决定系统的状态。
问题是我无法阅读问题,因为子进程可能不会刷新标准输出。所以我不能依赖于解析 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()
当然,实际的子进程是第三方二进制文件,我不能轻易修改它来添加必要的刷新调用,这将解决它。
我也可以尝试 Windows 中的 unbuffer
( What is the equivalent of unbuffer program on Windows? ),它被称为 winpty
,它(也许)允许我检测输出并解决我当前的问题问题,但我想保持简单,我想先解决标准输入问题...
我试过了...好吧,很多事情都行不通,包括尝试将伪造的文件作为 stdin
参数传递,这行不通,因为 subprocess
获取文件的 fileno
,我们不能把它当作垃圾...
p = subprocess.Popen(["python","ask.py"],...)
对字符串使用 communicate
也不起作用,因为您无法控制何时读取字符串以将其馈送到子进程(可能通过系统管道)。
这些问题很有前途,但要么依赖于标准输出,要么只适用于 Linux
- Detecting when a child process is waiting for input
- How can I know whether my subprocess is waiting for my input ?(in python3)
我目前正在做的是在超时的情况下运行该进程,如果达到超时,我就会决定该程序被阻止。但它会花费超时等待时间。如果我能在 stdin
被子进程读取时就做出决定,那会更好。
我想知道是否有本地 python 解决方案(可能使用 ctypes
和 windows 扩展)来检测从标准输入读取。但是不使用 Python 而使用非 Microsoft 专有语言的 native 解决方案可以做到。
最佳答案
如果我们不想让子进程处理用户输入,但在这种情况下只是简单地杀死它,解决方案可以是下一个:
- 使用重定向的 stdin 启动子进程到管道。
- pipe server端我们以异步方式创建,主要设置pipe 缓冲区大小为 0
- 在开始 child 之前 - 将 1 个字节写入此管道。
- 因为管道缓冲区大小为 0 - 操作未完成,直到另一个 边不读这个字节
- 在我们写入这 1 个字节和正在进行的操作(待定)之后 - 启动子进程。
- 终于开始等待先完成什么:写操作还是子进程?
- 如果先写完成 - 这意味着子进程开始读取 来自 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);
}
}
另一种代码变体——使用事件完成,而不是 apc。然而这并不影响最终结果。此代码变体给出与第一个完全相同的结果:
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);
}
}
关于python - 如何检测子进程何时要求在 Windows 中输入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49408302/