【问题标题】:CreateProcess stdout with CreateNamedPipe Overlapped与 CreateNamedPipe 重叠的 CreateProcess 标准输出
【发布时间】:2019-10-08 21:19:16
【问题描述】:

我正在尝试读取从 CreateProcess 开始的外部进程 (pgdump) 的标准输出。我使用匿名管道进行了此操作,但随后输出被阻止,并且不像我通过命令行执行它时那样(缺少最终输出)。我已经阅读了很多帖子,发现我需要 CreateNamedPipes 和 WaitForSingleObject 但我似乎无法让它工作。这是我使用匿名管道的工作代码,但被阻止了,我错过了输出的结尾

#include <QDebug>
#include <QString>

#include <windows.h>
#include <sstream>
#include <iostream>
#include <random>

int main()
{   
    #define BUFFERSIZE 256

    std::string program = "\"C:\\Program Files (x86)\\PostgreSQL\\10\\bin\\pg_dump.exe\"" +
            std::string( " --dbname=postgresql://postgresUser:PostGresql13@127.0.0.1:5432/employee -j1 -Fd -b -v -f "
                         "C:\\development\\myproject\\Build\\debug\\1-export.psql");

    HANDLE hReadStdOut = NULL;
    HANDLE hWriteStdOut = NULL;


    SECURITY_ATTRIBUTES saAttr;
    PROCESS_INFORMATION piProcInfo;
    STARTUPINFO siStartInfo;

    ZeroMemory( &saAttr, sizeof( SECURITY_ATTRIBUTES ));
    ZeroMemory( &piProcInfo, sizeof( PROCESS_INFORMATION ));
    ZeroMemory( &siStartInfo, sizeof( STARTUPINFO ));

    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;

    OVERLAPPED sOverlap;


    if( !CreatePipe(&hReadStdOut, &hWriteStdOut, &saAttr, 0) )
    {
        std::ostringstream os;
        os << GetLastError();

        qDebug() << "create pipe error : " << QString::fromStdString( os.str());
    }


    TCHAR* szCmdline = new TCHAR[ program.size() + 1];
    szCmdline[ program.size()] = 0;
    std::copy( program.begin(), program.end(), szCmdline );

    siStartInfo.cb = sizeof(STARTUPINFO);
    siStartInfo.hStdError = hWriteStdOut;
    siStartInfo.hStdOutput = hWriteStdOut;
    siStartInfo.hStdInput = NULL;
    siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

    BOOL bSuccess = CreateProcess( NULL, szCmdline, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &siStartInfo,&piProcInfo );

    if ( ! bSuccess )
    {
        std::ostringstream os;
        os << GetLastError();

        qDebug() << "create process error : " << QString::fromStdString( os.str());
    }
    else
    {
        CloseHandle( hWriteStdOut );

        DWORD err;
        DWORD nBytesRead;
        char buf[BUFFERSIZE + 1];

        int i(1);

        for(;;)
        {
            std::cout << "iteration " << std::to_string( i ) << std::endl;

            if( !ReadFile( hReadStdOut, buf, sizeof( buf), &nBytesRead, NULL) || !nBytesRead )
            {}

            if( GetLastError() == ERROR_SUCCESS )
            {
            }

            std::cout.flush();
            buf[nBytesRead] = '\0';
            std::string string_ = buf;
            std::cout << string_ << std::endl;

            std::size_t found = string_.find("contents of");

            if( !nBytesRead )
                break;

            i++;
        }

        CloseHandle(piProcInfo.hProcess);
        CloseHandle(piProcInfo.hThread);
    }

    return 0;
}

【问题讨论】:

  • siStartInfo.hStdError = pipe;//hWriteStdOut; siStartInfo.hStdOutput = pipe;//hWriteStdOut; - 您显示的代码中没有 pipe 变量。至于阻塞问题,您可以使用PeekNamedPipe() 和匿名管道来检测有多少数据可供读取,然后再读取。在任何情况下,您都说您在使用命名管道时遇到了问题,但是这段代码中没有命名管道。看看Overlapped I/O on anonymous pipe
  • @RemyLebeau,是的,你是对的我认为我的工作代码是一个好的开始,所以也许有人可以修改它,这样我就会明白我做错了什么
  • windows 中只有一种常见的管道类型。管道是否有名称 - 对他的行为没有影响。所以命名管道和匿名(未命名)管道之间没有任何区别。同步管道上的死锁经常是因为同步文件上的所有 i/o 操作都是序列化的 - 新的没有开始,直到旧的没有完成。因为这通常使用 2 个不同的管道对进行输入和输出,以避免死锁。但在你的情况下,你没有输入管道 - 不清楚它在哪里。异步管道永远不会出现死锁问题,因为 i/o 未序列化。但客户端几乎从未准备好使用异步管道
  • 所以这里的解决方案可以在服务器端使用异步管道,在客户端使用同步端。在这种情况下,通常我们可以使用单管道对(双工管道)来代替 2. 但同样 - 因为您的客户端 pg_dump.exe 只能写入管道 - 不清楚死锁在哪里或什么
  • @RbMm,你能告诉我如何修改我现有的代码或我另一篇文章中提到的代码吗?

标签: c++ winapi


【解决方案1】:

例如,如果你的 pg_dump.exe 只写一行到标准输出。有几种方法:

  1. std::cout &lt;&lt; "Hello World!\n";
  2. printf("Hello World!\n");
  3. std::cout &lt;&lt; "Hello World!\n"&lt;&lt; std::endl;
  4. WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), "Hello World!\n", 14, &amp;dwWritten, NULL);

如果 pg_dump.exe exit 在写入该行后,ReadFile 不会被阻塞并返回“Hello World!”行。

但是,如果 pg_dump.exe 不退出 后写入该行并继续其他工作。前两种写入方式(1.2.)将导致ReadFile 被阻止。但是,如果您使用第三种或第四种方式,ReadFile 将阻塞并返回“Hello World!”行。

这里的区别是前两种写入方式(std::cout &lt;&lt; "Hello World!\n";printf("Hello World!\n");)在写入结束时没有刷新操作,但第三种或第四种方式有。 std::endlWriteFile 刷新输出缓冲区。

总结

刷新输出以将其写入底层流(可能是文件、终端或管道)。在以下条件下刷新标准输出:

  1. 程序正常结束时。
  2. 最后使用std::endl
  3. 使用WriteFile

您可以检查是否是您的情况。

【讨论】:

  • 韩,我无法控制外部程序,如果这就是你的意思。如您所见,我尝试了一个flush(),但这没有任何区别(除非我放错了)。我只是不明白为什么 pg_dump.exe 的输出(在速度和行数方面)与我将它与 CreateProcess 和管道一起使用时如此不同。
  • @xyfix 您可以创建一个简单的控制台应用程序替换 pg_dump.exe 以查看它是否可以重现相同的问题。
  • 我创建了一个简单的控制台应用程序,它将循环中的行写入 std::out 并且每行都以 std::endl 结尾。我还在循环中放了一个flush()。但是使用如上所述的当前代码,当我直接在控制台中执行它时,我仍然没有得到我所看到的行。我认为我上面的代码需要一些调整,但我似乎无法弄清楚
  • @xyfix 请准确展示您的简单控制台应用程序和调用者应用程序您用于测试的内容。因为你展示的都是target pg_dump.exe。你没有从ReadFile 得到什么吗?你的简单控制台是否有同样的问题:“我在读取文件上收到错误 536“等待进程打开管道的另一端”。”?
  • @Han #include <iostream> #include <chrono> #include <thread> #include <random> int main() { int i(0); for(;;) { 自动种子 = std::chrono::system_clock::now().time_since_epoch().count();标准::mt19937 mt(种子); std::uniform_int_distribution<int> dist(1, 500); std::cout </int></random></thread></chrono></iostream>
猜你喜欢
  • 2010-12-06
  • 1970-01-01
  • 2017-09-02
  • 1970-01-01
  • 1970-01-01
  • 2021-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多