11

背景:


我正在开发一个程序,该程序需要能够捕获程序的stdout,stderr和返回值。理想情况下,我想将这些捕获在一个字符串中,该字符串存储在我的对象中,该对象包含过程的详细信息。我目前有一些代码可以通过使用一些(在我看来)古老的 C 文件句柄魔法将输出保存到文件中来工作。每当我想输出结果时,我都会打开该文件并打印内容。

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

问题陈述:


我正在寻找一种方法,将stdoutWindows 中创建的进程的输出保存到一个字符串,然后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
  • 我想继续使用多个进程,而不是线程,因为我需要运行的内容才能拥有自己的进程的自由

编辑:


额外说明:我还尝试在调用运行给定代码的函数后立即等待该过程完成,因此当时我可以使用stdout和的结果stderr

4

3 回答 3

21

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;
}
于 2013-01-03T23:55:32.690 回答
4

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
于 2018-02-01T00:25:34.673 回答
3

您必须使用管道来捕获进程的标准输出流的内容。MSDN上有一个详细的例子来说明如何做到这一点:

MSDN:创建具有重定向输入和输出的子进程

于 2013-01-03T22:10:46.503 回答