【问题标题】:Can not read from child process pipe无法从子进程管道中读取
【发布时间】:2015-07-03 14:45:30
【问题描述】:

我正在拼命地尝试创建一个子进程并将其输出重定向到新管道并从这些管道中读取,但我就是无法让它工作。我是 Win32API 的新手,请善待我。 :)

在“正常”使用 Win32API 失败后,我创建了包装器以专注于查找 API 调用的逻辑和/或顺序中的错误。您可以在下面找到包装器的接口。由于大多数方法直接转换为 Win32API 调用,因此(希望)不会成为回答这个问题的障碍。

使用包装器类的行为与我最初体验过的相同。

我已经阅读了很多关于这个主题的在线资源,其中一个说的与另一个不同。到目前为止最有用的是https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx,尤其是这个信息(强调我的):

父进程使用这两个管道的相对端来写入子进程的输入并从子进程的输出中读取。正如 STARTUPINFO 结构中所指定的,这些句柄也是可继承的。但是,不得继承这些句柄。因此,在创建子进程之前,父进程使用SetHandleInformation函数来保证子进程标准输入的写句柄和子进程标准输入的读句柄不能被继承。有关详细信息,请参阅管道。

在我找到这个主题并从父进程端关闭我不使用的端之前,我在子进程的标准输出读取句柄上永远阻塞ReadFile()。现在,它总是立即返回管道已损坏。

这就是我创建管道和流程的方式:

Popen(const String& command, const String& args,
      Bool use_current_pipes = false, Bool merge_stderr = true)
{
    Bool ok = true;
    _error = 0;
    ZeroMemory(&_pi, sizeof(_pi));
    STARTUPINFO si;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);

    if (!use_current_pipes) {
        // Create pipes for standard input, output and error.
        _stdin = Pipe(true);
        _stdout = Pipe(true);
        if (_stdout && merge_stderr)
            _stderr = _stdout.Duplicate();
        else
            _stderr = Pipe(true);

        if (_stdin && _stdout && _stderr) {
            _stdin.w.SetInheritable(false);
            _stderr.r.SetInheritable(false);
            _stdout.r.SetInheritable(false);

            si.hStdInput = _stdin.r.Get();
            si.hStdOutput = _stdout.w.Get();
            si.hStdError = _stderr.w.Get();
            si.dwFlags |= STARTF_USESTDHANDLES;
        }
        else {
            ok = false;
        }
    }
    else {
        si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
        si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
        si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
        si.dwFlags |= STARTF_USESTDHANDLES;
    }

    // Create the process. Enclose the actual command in quotes.
    ok = ok && CreateProcess(
        nullptr,  // command might contain whitespace, pass it quoted in arg 2 instead.
        AutoString("\"" + command + "\" " + args),
        nullptr,  // Process handle not inheritable
        nullptr,  // Thread handle not inheritable
        true,     // handles are inherited
        0,        // No creation flags
        nullptr,  // Use parent's environment block
        nullptr,  // Use parent's starting directory
        &si,      // Pointer to STARTUPINFO
        &_pi);    // Pointer to PROCESS_INFORMATION

    // Something went wrong? Well, bad.
    if (!ok) {
        _error = GetLastError();
    }

    // Close the handles that have been inherited by the child process
    // and to which we don't need access to, otherwise they will not
    // close when the child exits.
    _stdin.r.Close();
    _stdout.w.Close();
    _stderr.w.Close();
}

这就是我从标准输出 (_stdout.r) 读取的方式:

UInt Read(UInt num_bytes, char* buffer) {
    if (!_stdout.r) return 0;
    DWORD bytes_read = 0;
    if (!ReadFile(_stdout.r.Get(), buffer, num_bytes - 1, &bytes_read, nullptr)) {
        _error = GetLastError();
        ConsoleOut("[ERROR]: ReadFile() : " + String::IntToString((Int32) _error));
        if (_error == ERROR_BROKEN_PIPE) {
            ConsoleOut("No Wait, the Pipe is just broken.");
            _error = 0;  // that's fine
        }
        return 0;
    }
    buffer[bytes_read] = '\0';
    return bytes_read;
}

当我注释掉 Popen 构造函数的最后几行(关闭父进程中未使用的管道句柄)时,ReadFile() 将永远阻塞。启用这些行后,管道总是会立即中断(子进程很快退出)。

问题

  • 有人能看出我的代码/逻辑有什么问题吗?
  • 如果没有,如果有打开子进程并读取其输出的完整工作示例,我将不胜感激

包装接口

struct Handle {
    HANDLE h;
    explicit Handle();
    explicit Handle(HANDLE h);
    Handle(Handle&& other);
    Handle& operator = (Handle&& other);
    ~Handle();
    void Close();
    HANDLE Get();
    HANDLE Release();
    Handle Duplicate(DWORD options = DUPLICATE_SAME_ACCESS, HANDLE src_proc = nullptr, HANDLE dst_proc = nullptr) const;
    DWORD GetInfo() const;                        // uses GetHandleInformation
    void SetInheritable(bool inheritable) const;  // uses SetHandleInformation
    bool GetInheritable() const;
    operator bool() const;

    explicit Handle(const Handle&) = delete;
    Handle* operator = (const Handle&) = delete;
};
struct Pipe {
    Handle r, w;
    DWORD error;
    explicit Pipe();
    explicit Pipe(bool inheritable);
    Pipe(Pipe&& other);
    Pipe& operator = (Pipe&& other);
    ~Pipe();
    void Close();
    Pipe Duplicate(DWORD options = DUPLICATE_SAME_ACCESS, HANDLE src_proc = nullptr, HANDLE dst_proc = nullptr) const;
    operator bool() const;
    explicit Pipe(const Pipe&) = delete;
    Pipe* operator = (const Pipe&) = delete;
};

【问题讨论】:

  • 您确定子进程实际上正在写入标准输出吗?您是否尝试过使用命令行重定向 child.exe > output.txt 来查看它是否有效?
  • 这几行可能有问题,有一个临时的Pipe 对象被立即销毁:_stdin = Pipe(true); _stdout = Pipe(true); 但是你没有显示重要的代码。

标签: winapi pipe readfile createprocess


【解决方案1】:

如果不使用线程或重叠 I/O,您将​​面临死锁的风险。子进程可能正在尝试从其stdin 读取或等待其stdout 缓冲区中的空间以便它可以写入,您无法判断哪个,并且当您选择错误时,您会得到观察到的行为。子输出上的阻塞读表示你猜错了,它实际上是在等待输入。

阅读 Raymond Chen 的博客文章 Be careful when redirecting both a process's stdin and stdout to pipes, for you can easily deadlock,我今天在您之前的问题中也链接了该文章。它特别指出了您在问题中链接的同一个样本中的可怕破坏。

【讨论】:

  • 他实际上并没有陷入僵局,管道立即破裂。
  • @HarryJohnston:他已经知道哪一行代码破坏了管道,在问题“启用这些行后,管道总是立即断开”
  • 如果父进程保留其子句柄的副本,则管道将无限期保持打开状态,即使子进程已退出。但这不是期望的行为。删除这些行会隐藏问题,但并不能解决问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-06
  • 2021-10-13
  • 1970-01-01
  • 2013-07-12
相关资源
最近更新 更多