Cattura l’output del processo generato per la stringa

Sfondo:


Sto lavorando su un programma che deve essere in grado di catturare i valori di stdout , stderr e return di un programma. Idealmente, mi piacerebbe catturarli in una stringa che immagazzino all’interno del mio object che contiene i dettagli del processo. Al momento ho un codice che funziona salvando l’output in un file usando alcuni (a mio parere) archiico file C handle magic. Ogni volta che voglio produrre i risultati, apro quel file e stampo i contenuti.

Qualche volta (quando un processo che spawn viene lasciato in esecuzione) la successiva esecuzione del mio eseguibile si interromperà perché non è ansible aprire il file per la scrittura.

Dichiarazione problema:


Sto cercando un modo per salvare l’output dallo stdout di un processo creato in Windows a una stringa e lo stderr a un altro in un modo più sicuro e più moderno. In questo modo potrei stampare quei contenuti ogni volta che mi sembra di emettere il risultato di ogni processo creato.

Il mio brutto codice:


pezzo principale-

  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- (puoi pensare a questo solo come CreateProcess, ma nel caso qualcuno abbia bisogno di vedere cosa succede qui)

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

Gli appunti:


  • Sto usando VS 2010
  • Voglio continuare a utilizzare più processi, non thread perché ho bisogno di ciò che cerco per avere la libertà del proprio processo

Modificare:


Una nota in più: cerco anche di aspettare che il processo finisca subito dopo aver chiamato la funzione che esegue il codice dato, quindi i risultati di stdout e stderr sono disponibili per me in quel momento.

Dovrai utilizzare le pipe per acquisire il contenuto del stream stdout del tuo processo. C’è un esempio elaborato su MSDN su come ottenere ciò:

MSDN: creazione di un processo figlio con input e output reindirizzati

La risposta di Eddy Luten mi ha portato in una buona direzione, ma la documentazione MSDN (anche se elaborata) aveva alcuni problemi. Principalmente, devi assicurarti di chiudere tutte le maniglie che non usi. Inoltre ha solo il codice che si aspetta che l’utente capisca.

Quindi, invece, ecco il mio muro di codice che mi aspetto che la gente capisca semplicemente: 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; } 

Il codice Shawn Blakesley è una buona rielaborazione del codice di esempio di Microsoft, ma ha un po ‘di problemi quando ci sono stdout e stderr enormi flussi interlacciati che sono fuori uso. E alcune maniglie sono trapelate (che è OK per il codice di esempio). Avere le chiamate in background thread e PeekNamedPipe () si assicura che il codice si comporti più simile alla chiamata di 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