python - 如何检测子进程何时要求在 Windows 中输入

标签 python windows subprocess io-redirection

我有一个子进程要么退出并返回代码,要么询问一些事情并等待用户输入。

我想检测进程何时提出问题并立即退出。进程是否提出问题这一事实足以让我决定系统的状态。

问题是我无法阅读问题,因为子进程可能不会刷新标准输出。所以我不能依赖于解析 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

我目前正在做的是在超时的情况下运行该进程,如果达到超时,我就会决定该程序被阻止。但它会花费超时等待时间。如果我能在 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/

相关文章:

c - 在 Windows 和 Linux 上的 CMake 文件中链接数学库

python - 如何在存储控制台实时输出的同时运行 python 脚本?

python - 使用 shell=True w/list 时忽略 subprocess.call() 参数

python - 网络x : Convert multigraph into simple graph with weighted edges

python - Matplotlib:覆盖 "ggplot"默认样式属性

python - 使用 sklearn 在 Python 中进行典型相关分析

python - 如何使用 python subprocess 命令在不同目录中运行 shell 命令?

python - 是否有任何 python 模块可以在文本模式下绘制二维笛卡尔坐标?

ruby-on-rails - rmagick gem 安装在 Windows 上但捆绑失败

windows - 在 Windows 上从另一个进程调用函数