Here's sample MS compiler output:

The first three lines are always printed to stderr, followed by usage line printed to stdout. cmd.exe is the interpreting shell that runs the cl.exe process, reads stderr/stdout from it and draws the text to the console output. It always outputs it in this order, without ever stdout appearing ahead of stderr (perhaps, they do fflush(stderr) before printing usage line).
If I use boost::process no matter what I tried, it pretty much always didn't match output order of what cmd.exe was showing.
I use this sample program to produce output:
/// test-exe.exe
#include <string>
#include <stdio.h>
int main()
{
int lineLen = 1024;
int stdoutCount = 5;
int stderrCount = 5;
int repeatCount = 10;
std::string s;
s.resize(lineLen);
for (int i = 0; i < lineLen; ++i)
s[i] = '0' + (i / 10) % 10;
int n = 0;
for (int i = 0; i < repeatCount; ++i)
{
for (int i = 0; i < stdoutCount; ++i)
fprintf(stdout, "stdout %d %s\n", n++, s.c_str());
for (int i = 0; i < stderrCount; ++i)
fprintf(stderr, "stderr %d %s\n", n++, s.c_str());
}
}
How to properly do that with boost::process::child so that combined output would match what gets shown with regular cmd prompt?
One way is to use boost::process::system and redirect stderr to stdout (eg system("test.exe 2>&1")), but that's a workaround, since the streams are processed by cmd.exe under the hood and I get combined stdout without any distinction if something is stdout or stderr.
I used this code as starting point and tried to modify it in different ways, but it didn't work for me. It doesn't get deadlocked reading lines from stdout/stderr, but the output order doesn't get preserved the same way as cmd.exe gets it.
#include <boost/process.hpp>
#include <thread>
#include <string>
#include <vector>
struct OutputLine
{
std::string data; // with trailing /r/n stripped if any
bool isStderr; // if the line is stderr
};
static int getCmdOutput(const char* cmd, std::vector<OutputLine>& out)
{
namespace bp = boost::process;
out.clear();
std::vector<std::string> ret;
bp::ipstream s_out, s_err;
std::error_code ec;
bp::child c(cmd, bp::std_err > s_err, bp::std_out > s_out, ec);
if (ec)
return -1;
std::mutex mx, mx2;
bool readingStdout = false;
auto readFunc = [&]() {
for (std::string line;;)
{
bool doStdout = false;
{
std::scoped_lock lock(mx2);
if (!readingStdout)
doStdout = readingStdout = true;
}
bool res = !!std::getline(doStdout ? s_err : s_out, line);
{
std::scoped_lock lock(mx2);
if (doStdout)
readingStdout = false;
}
if (!res)
break;
while (!line.empty() && (line.back() == '\r' || line.back() == '\n'))
line.pop_back();
std::scoped_lock lock(mx);
out.emplace_back();
out.back().data.swap(line);
out.back().isStderr = doStdout;
}
};
std::thread th(readFunc); // read stdout/stderr from async thread
readFunc(); // read stdout/stderr
c.wait();
th.join();
return c.exit_code();
}
int main(int argc, const char* argv[])
{
std::vector<OutputLine> out;
int ret = getCmdOutput("test-exe.exe", out);
for (const auto& s : out)
printf("%s\n", s.data.c_str());
printf("test-exe.exe return: %d", ret);
}
cmd.exe isn’t really reading the output from its child processes: their output goes directly to the console (“terminal emulator” in Unix parlance). When nothing is redirected, stdout and stderr are the same file, and since stdout is line-buffered complete lines simply appear in the order that are written.
You can mostly get this behavior simply by using the same pipe for the child’s stdout and stderr (which is what 2>&1 does, but outside the command), but then it won’t be a terminal and stdout buffering becomes relevant. The only real answer is to use a pseudoterminal, which is non-trivial on Windows.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With