【问题标题】:std::getline breaks if stdin is replaced by pipe of another process如果 stdin 被另一个进程的管道替换,则 std::getline 中断
【发布时间】:2013-03-07 10:45:32
【问题描述】:

我有一个简单的示例程序,它从 std::cin 读取并写入 std::cout。如果在 cmd.exe 或 Visual Studio 调试器中运行,它工作正常。代码(server.cpp):

#include <iostream>
#include <istream>
#include <ostream>
#include <string>

int main(int argc, char* argv[])
{
    std::string input;
    while (std::getline(std::cin, input))
    {
        if (input == "dog")
        {
            std::cout << "cat" << std::endl;
        }
        else if (input == "white")
        {
            std::cout << "black" << std::endl;
        }
        else if (input == "quit")
        {
            std::cout << "exiting" << std::endl;
            return 0;
        }
        else if (input != "")
        {
            std::cout << "unknown" << std::endl;
        }
    }

    std::cout << "error" << std::endl;
}

现在我想从另一个写入其标准输入并从标准输出读取的进程运行它。我创建两个管道并使用 CreateProcess 启动一个进程,其中一个管道的读取句柄作为 StdInput 句柄,另一个管道的写入句柄作为 Stdouput 句柄。代码(client.cpp):

#include <Windows.h>

#include <cassert>
#include <iostream>
#include <ostream>
#include <string>

namespace
{
    class Server
    {
    public:
        Server() :
            m_pi()
        {
            SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES)};
            sa.bInheritHandle = TRUE;
            assert(CreatePipe(&m_ro, &m_wo, &sa, 0));
            assert(SetHandleInformation(m_ro, HANDLE_FLAG_INHERIT, 0));
            assert(CreatePipe(&m_ri, &m_wi, &sa, 0));
            assert(SetHandleInformation(m_ri, HANDLE_FLAG_INHERIT, 0));

            STARTUPINFO si = {sizeof(STARTUPINFO)};
            si.dwFlags |= STARTF_USESTDHANDLES;
            si.hStdInput = m_ri;
            si.hStdOutput = m_wo;

            assert(CreateProcess(L"..\\Debug\\server.exe", 0, 0, 0, 1, 0, 0, 0, &si, &m_pi));
        }

        ~Server()
        {
            execute("quit\n");
            assert(WaitForSingleObject(m_pi.hProcess, INFINITE) != WAIT_FAILED);

            assert(CloseHandle(m_pi.hThread));
            assert(CloseHandle(m_pi.hProcess));
            assert(CloseHandle(m_wi));
            assert(CloseHandle(m_ri));
            assert(CloseHandle(m_wo));
            assert(CloseHandle(m_ro));
        }

        std::string execute(std::string const& cmd)
        {
            DWORD num_bytes;
            assert(WriteFile(m_wi, cmd.c_str(), (DWORD)cmd.size(), &num_bytes, 0));

            std::string output;

            DWORD n = 0;
            while (n == 0)
            {
                Sleep(0);

                assert(PeekNamedPipe(m_ro, 0, 0, 0, &n, 0));
                if (n > 0)
                {
                    output.resize(n);
                    assert(ReadFile(m_ro, &output[0], n, &num_bytes, 0));
                }
            }

            return output;
        }

    private:
        HANDLE m_wo, m_ro, m_wi, m_ri;
        PROCESS_INFORMATION m_pi;
    };

    Server g_server;
}

int main(int argc, char* argv[])
{
    std::cout << g_server.execute("white\n") << std::endl;
    std::cout << g_server.execute("foobar\n") << std::endl;
    std::cout << g_server.execute("dog\n") << std::endl;
}

问题是客户端只接收到按摩“错误”,所以服务器的std::cin好像坏了。

我的问题是,我做错了什么?

【问题讨论】:

  • “我做错了什么?” - 首先,您编码while (true) { getline... } 而不是while (std::getline(std::cin, input)) { use input... }。解决这个问题,您至少会注意到您的输入尝试遇到 EOF/断开连接。
  • @Tony D:谢谢,我相应地改变了问题

标签: c++ winapi visual-c++ process pipe


【解决方案1】:

您正在禁用子句柄的继承,该句柄将用于从 stdin 读取 - 子句需要继承该句柄。而不是:

SetHandleInformation(m_ri, HANDLE_FLAG_INHERIT, 0);

尝试以下操作以禁用服务器进程将用于写入子级stdin 的句柄上的继承:

SetHandleInformation(m_wi, HANDLE_FLAG_INHERIT, 0);

【讨论】:

  • 谢谢,就是这样。但是代码也可以在没有任何调用 SetHandleInformation 的情况下工作。我的来源是这个例子:link 但我不确定为什么有必要禁用管道句柄之一的继承。
  • 我认为将句柄标记为不可继承的原因是:1)保护它们不被子进程意外使用,因为子进程与管道的那些端没有业务(只是一般进程=资源的级别保护)和2)如果子进程是一个长期存在的进程(可能它比父进程寿命更长),它就没有办法关闭句柄,因此管道会“泄漏”。跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-21
相关资源
最近更新 更多