将Spawned Process的输出捕获到字符串

时间:2013-01-03 20:57:52

标签: c++ logging parallel-processing

背景


我正在开发一个程序,需要能够捕获stdoutstderr并返回程序的值。理想情况下,我想在我存储在我的对象中的字符串中捕获这些字符串,该字符串包含进程的详细信息。我目前有一些代码,通过使用一些(在我看来)古老的C文件句柄魔术将输出保存到文件中。任何时候我想输出结果,我打开该文件,然后打印内容。

有时候(当我生成的进程继续运行时)我的可执行文件的下一次执行将会中断,因为它无法打开文件进行写入。

问题陈述:


我正在寻找一种方法,将Windows中已创建进程的stdout的输出保存为一个字符串,将stderr的输出保存为更安全,更现代的方式。这样我就可以在输出每个创建过程的结果的任何时候打印这些内容。

我丑陋的代码:


主要块 -

    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 - (您可以将其视为CreateProcess,但万一有人需要查看此处发生的事情)

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

备注:


  • 我正在使用VS 2010
  • 我想继续使用多个进程,而不是线程,因为我需要运行的东西才能拥有自己的进程

修改


额外注意事项:在调用运行代码的函数后,我也会尝试等待进程完成,因此当时我可以使用stdoutstderr的结果

2 个答案:

答案 0 :(得分:18)

Eddy Luten的回答让我朝着一个好方向前进,但MSDN文档(虽然精心制作)有一些问题。主要是,您需要确保关闭所有不使用的句柄。它还有它希望用户理解的代码。

相反,这是我希望人们能够理解的代码墙: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;
}

答案 1 :(得分:2)

Shawn Blakesley代码是Microsoft示例代码的良好返工,但是当存在大量stdout和stderr交错流时,它有一些问题。并且一些句柄被泄露(这对于示例代码是可以的)。使用后台线程和PeekNamedPipe()调用可确保代码的行为与POSIX系统调用更相似:

#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