Capture Output of Spawned Process to string

后端 未结 3 392
别跟我提以往
别跟我提以往 2020-11-29 10:52

Background:


I\'m working on a program that needs to be able to capture the stdout, stderr and return values of a pro

3条回答
  •  清歌不尽
    2020-11-29 11:17

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

提交回复
热议问题