Background:
I'm working on a program that needs to be able to capture the stdout
, stderr
and return values of a program. Ideally, I would like to capture these in a string that I store inside of my object that holds details of the process. I currently have some code that works by saving the output into a file using some (in my opinion) archaic C file handle magic. Any time I want to output the results, I open up that file and I print the contents.
Sometimes (when a process I spawn is left running) the next execution of my executable will break down because it cannot open the file for writing.
Problem Statement:
I'm looking for a way to save the output from stdout
of a created process in windows to one string and the stderr
to another in a safer, more modern fashion. That way I could print those contents any time I feel like outputting the result of each created process.
My ugly code:
main chunk-
int stdoutold = _dup(_fileno(stdout)); //make a copy of stdout
int stderrold = _dup(_fileno(stdout)); //make a copy of stderr
FILE *f;
if(!fopen_s(&f, "name_of_my_file", "w")){ //make sure I can write to the file
_dup2(_fileno(f), _fileno(stdout)); //make stdout point to f
_dup2(_fileno(f), _fileno(stderr)); //make stderr point to f
fork("command_I_want_to_run", &pi); //run my fake fork (see below)
}
else{
...//error handling
}
_close(_fileno(stdout)); //close tainted stdout
_close(_fileno(stderr)); //close tainted stderr
_close(_fileno(f)); //close f
_dup2(stdoutold, _fileno(stdout)); //fix stdout
_dup2(stderrold, _fileno(stderr)); //fix stderr
fork- (you can think of this as just CreateProcess, but just in case anyone needs to see what happens here)
int fork(std::string s, PROCESS_INFORMATION* pi){
char infoBuf[INFO_BUFFER_SIZE];
int bufCharCount =
ExpandEnvironmentStrings(s.c_str(), infoBuf, INFO_BUFFER_SIZE );
...
STARTUPINFO si;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( pi, sizeof(*pi) );
LPSTR str = const_cast<char *>(infoBuf);
if(!CreateProcess(NULL,
str,
NULL,
NULL,
TRUE,
0,
NULL,
NULL,
&si,
pi)
){
int err = GetLastError();
printf("CreateProcess failed (%d).\n", err);
CloseHandle((*pi).hProcess);
CloseHandle((*pi).hThread);
return err;
}
return 0;
}
Notes:
Edit:
An extra note: I also try to wait for the process to finish right after calling the function that runs the code given, so the results of stdout
and stderr
are available to me at that time.
Eddy Luten's answer led me in a good direction, but the MSDN documentation (while elaborate) had some issues. Mainly, you need to ensure you close all handles you don't use. Also it just has code it expects the user to understand.
So instead, here's my wall of code I expect people to just understand :D
#include <string>
#include <iostream>
#include <windows.h>
#include <stdio.h>
#pragma warning( disable : 4800 ) // stupid warning about bool
#define BUFSIZE 4096
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;
HANDLE g_hChildStd_ERR_Rd = NULL;
HANDLE g_hChildStd_ERR_Wr = NULL;
PROCESS_INFORMATION CreateChildProcess(void);
void ReadFromPipe(PROCESS_INFORMATION);
int main(int argc, char *argv[]){
SECURITY_ATTRIBUTES sa;
printf("\n->Start of parent execution.\n");
// Set the bInheritHandle flag so pipe handles are inherited.
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDERR.
if ( ! CreatePipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &sa, 0) ) {
exit(1);
}
// Ensure the read handle to the pipe for STDERR is not inherited.
if ( ! SetHandleInformation(g_hChildStd_ERR_Rd, HANDLE_FLAG_INHERIT, 0) ){
exit(1);
}
// Create a pipe for the child process's STDOUT.
if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &sa, 0) ) {
exit(1);
}
// Ensure the read handle to the pipe for STDOUT is not inherited
if ( ! SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0) ){
exit(1);
}
// Create the child process.
PROCESS_INFORMATION piProcInfo = CreateChildProcess();
// Read from pipe that is the standard output for child process.
printf( "\n->Contents of child process STDOUT:\n\n", argv[1]);
ReadFromPipe(piProcInfo);
printf("\n->End of parent execution.\n");
// The remaining open handles are cleaned up when this process terminates.
// To avoid resource leaks in a larger application,
// close handles explicitly.
return 0;
}
// Create a child process that uses the previously created pipes
// for STDERR and STDOUT.
PROCESS_INFORMATION CreateChildProcess(){
// Set the text I want to run
char szCmdline[]="test --log_level=all --report_level=detailed";
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
bool bSuccess = FALSE;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDERR and STDOUT handles for redirection.
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = g_hChildStd_ERR_Wr;
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the child process.
bSuccess = CreateProcess(NULL,
szCmdline, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
CloseHandle(g_hChildStd_ERR_Wr);
CloseHandle(g_hChildStd_OUT_Wr);
// If an error occurs, exit the application.
if ( ! bSuccess ) {
exit(1);
}
return piProcInfo;
}
// Read output from the child process's pipe for STDOUT
// and write to the parent process's pipe for STDOUT.
// Stop when there is no more data.
void ReadFromPipe(PROCESS_INFORMATION piProcInfo) {
DWORD dwRead;
CHAR chBuf[BUFSIZE];
bool bSuccess = FALSE;
std::string out = "", err = "";
for (;;) {
bSuccess=ReadFile( g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
if( ! bSuccess || dwRead == 0 ) break;
std::string s(chBuf, dwRead);
out += s;
}
dwRead = 0;
for (;;) {
bSuccess=ReadFile( g_hChildStd_ERR_Rd, chBuf, BUFSIZE, &dwRead, NULL);
if( ! bSuccess || dwRead == 0 ) break;
std::string s(chBuf, dwRead);
err += s;
}
std::cout << "stdout:" << out << std::endl;
std::cout << "stderr:" << err << std::endl;
}
Shawn Blakesley code is good rework of Microsoft sample code but it has a bit of a problem when there is massive stdout and stderr interleaved streams that are out of order. And some handles are leaked (which is OK for the sample code). Having background thread and PeekNamedPipe() calls makes sure the the code behave more similar to POSIX system call:
#include <windows.h>
#include <stdio.h>
#include <malloc.h>
#ifdef __cplusplus
#define BEGIN_C extern "C" {
#define END_C } // extern "C"
#define null nullptr
#else
#define BEGIN_C
#define END_C
#define null ((void*)0)
#endif
BEGIN_C
int system_np(const char* command, int timeout_milliseconds,
char* stdout_data, int stdout_data_size,
char* stderr_data, int stderr_data_size, int* exit_code);
typedef struct system_np_s {
HANDLE child_stdout_read;
HANDLE child_stderr_read;
HANDLE reader;
PROCESS_INFORMATION pi;
const char* command;
char* stdout_data;
int stdout_data_size;
char* stderr_data;
int stderr_data_size;
int* exit_code;
int timeout; // timeout in milliseconds or -1 for INIFINTE
} system_np_t;
static char stdout_data[16 * 1024 * 1024];
static char stderr_data[16 * 1024 * 1024];
int main(int argc, char *argv[]) {
int bytes = 1;
for (int i = 1; i < argc; i++) {
bytes += (int)strlen(argv[i]) + 1;
}
char* command = (char*)alloca(bytes);
command[0] = 0;
char* p = command;
for (int i = 1; i < argc; i++) {
int n = (int)strlen(argv[i]);
memcpy(p, argv[i], n); p += n;
*p = (i == argc - 1) ? 0x00 : 0x20;
p++;
}
int exit_code = 0;
if (command[0] == 0) {
command = (char*)"cmd.exe /c \"dir /w /b\"";
}
int r = system_np(command, 100 * 1000, stdout_data, sizeof(stdout_data), stderr_data, sizeof(stderr_data), &exit_code);
if (r != 0) {
fprintf(stderr, "system_np failed: %d 0x%08x %s", r, r, strerror(r));
return r;
} else {
fwrite(stdout_data, strlen(stdout_data), 1, stdout);
fwrite(stderr_data, strlen(stderr_data), 1, stderr);
return exit_code;
}
}
static int peek_pipe(HANDLE pipe, char* data, int size) {
char buffer[4 * 1024];
DWORD read = 0;
DWORD available = 0;
bool b = PeekNamedPipe(pipe, null, sizeof(data), null, &available, null);
if (!b) {
return -1;
} else if (available > 0) {
int bytes = min(sizeof(buffer), available);
b = ReadFile(pipe, buffer, bytes, &read, null);
if (!b) {
return -1;
}
if (data != null && size > 0) {
int n = min(size - 1, (int)read);
memcpy(data, buffer, n);
data[n + 1] = 0; // always zero terminated
return n;
}
}
return 0;
}
static DWORD WINAPI read_from_all_pipes_fully(void* p) {
system_np_t* system = (system_np_t*)p;
unsigned long long milliseconds = GetTickCount64(); // since boot time
char* out = system->stdout_data != null && system->stdout_data_size > 0 ? system->stdout_data : null;
char* err = system->stderr_data != null && system->stderr_data_size > 0 ? system->stderr_data : null;
int out_bytes = system->stdout_data != null && system->stdout_data_size > 0 ? system->stdout_data_size - 1 : 0;
int err_bytes = system->stderr_data != null && system->stderr_data_size > 0 ? system->stderr_data_size - 1 : 0;
for (;;) {
int read_stdout = peek_pipe(system->child_stdout_read, out, out_bytes);
if (read_stdout > 0 && out != null) { out += read_stdout; out_bytes -= read_stdout; }
int read_stderr = peek_pipe(system->child_stderr_read, err, err_bytes);
if (read_stderr > 0 && err != null) { err += read_stderr; err_bytes -= read_stderr; }
if (read_stdout < 0 && read_stderr < 0) { break; } // both pipes are closed
unsigned long long time_spent_in_milliseconds = GetTickCount64() - milliseconds;
if (system->timeout > 0 && time_spent_in_milliseconds > system->timeout) { break; }
if (read_stdout == 0 && read_stderr == 0) { // nothing has been read from both pipes
HANDLE handles[2] = {system->child_stdout_read, system->child_stderr_read};
WaitForMultipleObjects(2, handles, false, 1); // wait for at least 1 millisecond (more likely 16)
}
}
if (out != null) { *out = 0; }
if (err != null) { *err = 0; }
return 0;
}
static int create_child_process(system_np_t* system) {
SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = true;
sa.lpSecurityDescriptor = null;
HANDLE child_stdout_write = INVALID_HANDLE_VALUE;
HANDLE child_stderr_write = INVALID_HANDLE_VALUE;
if (!CreatePipe(&system->child_stderr_read, &child_stderr_write, &sa, 0) ) {
return GetLastError();
}
if (!SetHandleInformation(system->child_stderr_read, HANDLE_FLAG_INHERIT, 0) ){
return GetLastError();
}
if (!CreatePipe(&system->child_stdout_read, &child_stdout_write, &sa, 0) ) {
return GetLastError();
}
if (!SetHandleInformation(system->child_stdout_read, HANDLE_FLAG_INHERIT, 0) ){
return GetLastError();
}
// Set the text I want to run
STARTUPINFO siStartInfo = {0};
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = child_stderr_write;
siStartInfo.hStdOutput = child_stdout_write;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
siStartInfo.wShowWindow = SW_HIDE;
bool b = CreateProcessA(null,
(char*)system->command,
null, // process security attributes
null, // primary thread security attributes
true, // handles are inherited
CREATE_NO_WINDOW, // creation flags
null, // use parent's environment
null, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&system->pi); // receives PROCESS_INFORMATION
int err = GetLastError();
CloseHandle(child_stderr_write);
CloseHandle(child_stdout_write);
if (!b) {
CloseHandle(system->child_stdout_read); system->child_stdout_read = INVALID_HANDLE_VALUE;
CloseHandle(system->child_stderr_read); system->child_stderr_read = INVALID_HANDLE_VALUE;
}
return b ? 0 : err;
}
int system_np(const char* command, int timeout_milliseconds,
char* stdout_data, int stdout_data_size,
char* stderr_data, int stderr_data_size, int* exit_code) {
system_np_t system = {0};
if (exit_code != null) { *exit_code = 0; }
if (stdout_data != null && stdout_data_size > 0) { stdout_data[0] = 0; }
if (stderr_data != null && stderr_data_size > 0) { stderr_data[0] = 0; }
system.timeout = timeout_milliseconds > 0 ? timeout_milliseconds : -1;
system.command = command;
system.stdout_data = stdout_data;
system.stderr_data = stderr_data;
system.stdout_data_size = stdout_data_size;
system.stderr_data_size = stderr_data_size;
int r = create_child_process(&system);
if (r == 0) {
system.reader = CreateThread(null, 0, read_from_all_pipes_fully, &system, 0, null);
if (system.reader == null) { // in theory should rarely happen only when system super low on resources
r = GetLastError();
TerminateProcess(system.pi.hProcess, ECANCELED);
} else {
bool thread_done = WaitForSingleObject(system.pi.hThread, timeout_milliseconds) == 0;
bool process_done = WaitForSingleObject(system.pi.hProcess, timeout_milliseconds) == 0;
if (!thread_done || !process_done) {
TerminateProcess(system.pi.hProcess, ETIME);
}
if (exit_code != null) {
GetExitCodeProcess(system.pi.hProcess, (DWORD*)exit_code);
}
CloseHandle(system.pi.hThread);
CloseHandle(system.pi.hProcess);
CloseHandle(system.child_stdout_read); system.child_stdout_read = INVALID_HANDLE_VALUE;
CloseHandle(system.child_stderr_read); system.child_stderr_read = INVALID_HANDLE_VALUE;
WaitForSingleObject(system.reader, INFINITE); // join thread
CloseHandle(system.reader);
}
}
if (stdout_data != null && stdout_data_size > 0) { stdout_data[stdout_data_size - 1] = 0; }
if (stderr_data != null && stderr_data_size > 0) { stderr_data[stderr_data_size - 1] = 0; }
return r;
}
END_C
You'll have to use pipes to capture the contents of your process's stdout stream. There's an elaborate example on MSDN on how to accomplish this:
MSDN: Creating a Child Process with Redirected Input and Output
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