2013-01-03 12 views
6

Tło:Przechwytywanie Wyjście zrodził proces ciąg


Pracuję na program, który musi być w stanie uchwycić stdout, stderr i powrotu wartości programu. Idealnie, chciałbym uchwycić je w ciągu, który przechowuję wewnątrz obiektu, który zawiera szczegóły procesu. Obecnie mam trochę kodu, który działa, zapisując dane wyjściowe do pliku za pomocą jakiejś (moim zdaniem) archaicznej magicznej obsługi pliku C. Za każdym razem, gdy chcę wyprowadzić wyniki, otwieram ten plik i drukuję jego zawartość.

Czasami (gdy proces odradzania jest włączony), następne wykonanie mojego pliku wykonywalnego zostanie przerwane, ponieważ nie może otworzyć pliku do zapisu. Oświadczenie

Problem:


szukam sposób, aby zapisać wyjście z stdout z utworzonego procesu w oknach na jednym ciągiem i stderr do drugiego w bezpieczniejszym, bardziej nowoczesny sposób . W ten sposób mogłem wydrukować te treści za każdym razem, kiedy chciałbym wydrukować wynik każdego stworzonego procesu.

Moja brzydki kod:


główny 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- (można pomyśleć o tym, jak właśnie CreateProcess, ale na wszelki wypadek ktoś musi zobacz, co się dzieje tutaj)

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

Uwagi:


  • używam VS 2010
  • chcę pozostać przy użyciu wielu procesów, a nie tematy, ponieważ muszę co biegnę mieć swobodę własnym procesie

Edit:


Dodatkowa uwaga: Próbuję również czekać na zakończenie procesu zaraz po wywołaniu funkcji, która uruchamia podany kod, więc wyniki stdout i stderr są dla mnie dostępne w tym czasie.

+0

related: [? Uruchomić exe/procesu z stdin stdout i stderr] (http://stackoverflow.com/questions/5485923/launch-an-exe-process-with-stdin-stdout-and-stderr/39648986). Zobacz także [tiny-process-library] (https://github.com/eidheim/tiny-process-library), co jest bardzo wygodne. – Delgan

Odpowiedz

4

Będziesz musiał użyć potoków, aby przechwycić zawartość strumienia wyjściowego swojego procesu. Jest skomplikowany przykład na MSDN, jak to osiągnąć:

MSDN: Creating a Child Process with Redirected Input and Output

+0

Wygląda na to, że to może wystarczyć. Zajrzę do tego i skontaktuję się z Tobą, gdy tylko spróbuję go w pełni wdrożyć. +1 –

17

odpowiedź Eddy Luten doprowadziła mnie w dobrym kierunku, ale w dokumentacji MSDN (podczas gdy skomplikowane) miał pewne problemy. Przede wszystkim musisz upewnić się, że zamykasz wszystkie uchwyty, z których nie korzystasz. Ma również kod, który oczekuje od użytkownika.

Zamiast więc, oto moja ściana kodu I oczekiwać, że ludzie po prostu zrozumieć: 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; 
} 
+0

Doceniam ten przykład kodu, ale myślę, że jest w nim błąd. Komentarz twierdzi, że stdin i stdout są przekierowywane, ale kod wskazuje, że stderr i stdout są przekierowywane. Ponieważ komentarze są tym, co sprawia, że ​​jest to bardziej przejrzyste niż artykuł MSDN, dobrze byłoby to naprawić. –

+0

Przyjemny połów. Wprowadziłem zmiany. –

+0

Dzięki za przykład kodu, ale czy możesz mi powiedzieć, dlaczego PROCESS_INFORMATION została przekazana do ReadFromPipe()? – randomuser15995183

1
kod

Shawn Blakesley jest dobre przerobienie kodu próbki Microsoft, ale ma trochę problem, gdy istnieje ogromne stdout i stderr przeplatane strumienie, które są nieczynne. Niektóre uchwyty są przeciekające (co jest w porządku dla przykładowego kodu). Mając wątek tła i PeekNamedPipe() wywołuje pilnuje zachowują się kod bardziej podobna do POSIX wywołania systemowego:

#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 
Powiązane problemy