Capture la salida del proceso generado a cadena

Fondo:


Estoy trabajando en un progtwig que debe ser capaz de capturar los valores stdout , stderr y return de un progtwig. Idealmente, me gustaría capturarlos en una cadena que almaceno dentro de mi objeto que contiene detalles del proceso. Actualmente tengo un código que funciona guardando la salida en un archivo usando algunos (en mi opinión) arcaico C manejar archivos mágicos. Cada vez que quiero mostrar los resultados, abro ese archivo e imprimo los contenidos.

Algunas veces (cuando un proceso que engendro se deja en ejecución) la próxima ejecución de mi ejecutable se romperá porque no puede abrir el archivo para escribir.

Planteamiento del problema:


Estoy buscando una manera de guardar la salida de stdout de un proceso creado en Windows a una cadena y la de stderr a otra de una manera más segura y más moderna. De esa manera podría imprimir esos contenidos cada vez que tenga ganas de sacar el resultado de cada proceso creado.

Mi feo código:


trozo principal-

  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- (puedes pensar en esto como solo CreateProcess, pero por si alguien necesita ver lo que sucede aquí)

 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(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; } 

Notas:


  • Estoy usando VS 2010
  • Quiero seguir usando múltiples procesos, no hilos, porque necesito lo que ejecuto para tener la libertad de su propio proceso

Editar:


Una nota adicional: también trato de esperar a que el proceso termine justo después de llamar a la función que ejecuta el código dado, por lo que los resultados de stdout y stderr están disponibles para mí en ese momento.

Tendrá que usar pipes para capturar el contenido de la stream estándar de su proceso. Hay un ejemplo elaborado en MSDN sobre cómo lograr esto:

MSDN: Crear un proceso secundario con entrada y salida redirigida

La respuesta de Eddy Luten me condujo en una buena dirección, pero la documentación de MSDN (aunque elaborada) tuvo algunos problemas. Principalmente, debes asegurarte de cerrar todos los identificadores que no uses. Además, solo tiene un código que espera que el usuario comprenda.

Entonces, en cambio, aquí está mi muro de código que espero que la gente entienda: D

 #include  #include  #include  #include  #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; } 

El código de Shawn Blakesley es una buena revisión del código de muestra de Microsoft, pero tiene un pequeño problema cuando hay streams masivos stdr y stderr intercalados que están fuera de servicio. Y algunos identificadores se filtraron (lo cual está bien para el código de muestra). Tener un hilo de fondo y llamadas PeekNamedPipe () aseguran que el código se comporta de forma más similar a la llamada al sistema POSIX:

 #include  #include  #include  #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