无法从子进程管道中读取

时间:2015-04-24 20:29:59

标签: winapi pipe readfile createprocess

我正在拼命尝试创建子进程并将其输出重定向到新管道并从这些管道读取,但我无法让它工作。我是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;
};

1 个答案:

答案 0 :(得分:0)

不使用任何线程或重叠的I / O,您就有可能出现死锁。子进程可能试图从其stdin读取或等待其stdout缓冲区中的空间,以便它可以写入,您无法分辨哪个,当您选择错误时,您将获得观察到的行为。对孩子输出的阻塞读取意味着你猜错了,它实际上在等待输入。

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