How do I launch a process and obtain its output?

2019-01-15 21:23发布

问题:

In the C language using the Windows API, how can I get the output of a process when I have its process information?

I have code like this:

STARTUPINFO si1;
ZeroMemory(&si1,sizeof(si1));
PROCESS_INFORMATION pi1;
ZeroMemory(&pi1,sizeof(pi1));
BOOL bRes1=CreateProcess(_T("C:\\User\\asd.exe"),cmd_line1,NULL,NULL,FALSE,CREATE_NO_WINDOW,NULL,NULL, &si1,&pi1); 

and the process asd.exe prints a certain output, I want to get it to my process(the one i used the code above in).

回答1:

This answer is probably a bit longer than you expected, but that's how the Windows API is sometimes. In any case, even though this takes more code than it initially seems like it should need, this at least provides a fairly clean, easy to use interface for programs that want to do things like this. The code is commented fairly liberally, explaining not only how to use the function it provides, but also how it's doing most of what it does, and what Windows requires if you decide to write code based on this instead of using it directly.

I should also point out that this is some fairly old code. It works well enough that I haven't had any reason to rewrite it, but if I was doing it again today, I'm pretty sure I'd do it quite a bit differently (for one thing, I'd undoubtedly use C++ instead of straight C, as I did here).

This does also contain some tidbits of code that are frequently useful for completely unrelated purposes (e.g., I've used system_error quite a few places -- it's Windows-only, but really incidental to spawning a child process).

Anyway, we'll start with spawn.h, which defines the interface to the code:

#ifndef SPAWN_H_INCLUDED_
#define SPAWN_H_INCLUDED_

// What to do if you ask to create a file and it already exists.
// We can fail to create it, overwrite the existing content, or append the
// new content to the existing content.
enum { FAIL, OVERWRITE, APPEND };

// This just specifies the type of a thread procedure to use to handle a stream
// to/from the child, if you decided to do that.
//
typedef unsigned long (__stdcall *ThrdProc)(void *);

// stream_info is the real core of the code. It's what lets you specify how
// to deal with a particular stream. When you call CreateDetchedProcess, 
// you need to pass the address of an array of three stream_info objects
// that specify the handling for the child's standard input, standard
// output, and standard error streams respectively. If you specify a
// filename, that stream will be connected to the named file. If you set
// filename to NULL, you can instead specify a procedure that will be
// started in a thread that will provide data for that stream, or process
// the data coming from that stream. Toward the bottom of spawn.c there are
// a couple of sample handlers, one that processes standard error, and the
// other that processes standard output from a spawned child process.
//
typedef struct {
    char *filename;
    ThrdProc handler;
    HANDLE handle;
} stream_info;

// Once you've filled in your stream_info structures, spawning the child is
// pretty easy: just pass the name of the executable for the child, and the
// address of the stream_info array. This handles most of the usual things:
// if you don't specify an extension for the file, it'll search for it with
// extensions of `.com", ".exe", ".cmd", and ".bat" in the current
// directory, and then in any directory specified by the PATH environment
// variable. It'll open/create any files you've specified in the
// stream_info structures, and create pipes for any streams that are to be
// directed to the parent, and start up threads to run any stream handlers 
// specified.
// 
HANDLE CreateDetachedProcess(char const *name, stream_info *streams);

#endif

Then the implementation of CreateDetachedProcess (along with some test/demo code):

#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <ctype.h>
#include <io.h>
#include <fcntl.h>
#include <stdlib.h>

#include "spawn.h"

static void system_error(char const *name) {
// A function to retrieve, format, and print out a message from the
// last error.  The `name' that's passed should be in the form of a
// present tense noun (phrase) such as "opening file".
//
    char *ptr = NULL;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM,
        0,
        GetLastError(),
        0,
        (char *)&ptr,
        1024,
        NULL);

    fprintf(stderr, "%s\n", ptr);
    LocalFree(ptr);
}

static void InitializeInheritableSA(SECURITY_ATTRIBUTES *sa) {

    sa->nLength = sizeof *sa;
    sa->bInheritHandle = TRUE;
    sa->lpSecurityDescriptor = NULL;
}


static HANDLE OpenInheritableFile(char const *name) {
    SECURITY_ATTRIBUTES sa;
    HANDLE retval;

    InitializeInheritableSA(&sa);

    retval = CreateFile(
        name,
        GENERIC_READ,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        &sa,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        0);


    if (INVALID_HANDLE_VALUE == retval) {
        char buffer[100];

        sprintf(buffer, "opening file %s", name);

        system_error(buffer);
        return retval;
    }
}

static HANDLE CreateInheritableFile(char const *name, int mode) {
    SECURITY_ATTRIBUTES sa;
    HANDLE retval;

    DWORD FSmode = mode ? OPEN_ALWAYS : CREATE_NEW;

    InitializeInheritableSA(&sa);

    retval = CreateFile(
        name,
        GENERIC_WRITE,
        FILE_SHARE_READ,
        &sa,
        FSmode,
        FILE_ATTRIBUTE_NORMAL,
        0);

    if (INVALID_HANDLE_VALUE == retval) {
        char buffer[100];

        sprintf(buffer, "creating file %s", name);

        system_error(buffer);
        return retval;
    }

    if ( mode == APPEND ) 
        SetFilePointer(retval, 0, 0, FILE_END);
}

enum inheritance { inherit_read = 1, inherit_write = 2 };

static BOOL CreateInheritablePipe(HANDLE *read, HANDLE *write, int inheritance) {

    SECURITY_ATTRIBUTES sa;

    InitializeInheritableSA(&sa);

    if ( !CreatePipe(read, write, &sa, 0)) {
        system_error("Creating pipe");
        return FALSE;
    }

    if (!inheritance & inherit_read)
        DuplicateHandle(
            GetCurrentProcess(),
            *read,
            GetCurrentProcess(),
            NULL,
            0,
            FALSE,
            DUPLICATE_SAME_ACCESS);

    if (!inheritance & inherit_write) 
        DuplicateHandle(
            GetCurrentProcess(),
            *write,
            GetCurrentProcess(),
            NULL,
            0,
            FALSE,
            DUPLICATE_SAME_ACCESS);

    return TRUE;
}

static BOOL find_image(char const *name, char *buffer) {
// Try to find an image file named by the user.
// First search for the exact file name in the current
// directory.  If that's found, look for same base name
// with ".com", ".exe" and ".bat" appended, in that order.
// If we can't find it in the current directory, repeat
// the entire process on directories specified in the
// PATH environment variable.
//
#define elements(array) (sizeof(array)/sizeof(array[0]))

    static char *extensions[] = {".com", ".exe", ".bat", ".cmd"};
    int i;
    char temp[FILENAME_MAX];

    if (-1 != access(name, 0)) {
        strcpy(buffer, name);
        return TRUE;
    }

    for (i=0; i<elements(extensions); i++) {
        strcpy(temp, name);
        strcat(temp, extensions[i]);
        if ( -1 != access(temp, 0)) {
            strcpy(buffer, temp);
            return TRUE;
        }
    }

    _searchenv(name, "PATH", buffer);
    if ( buffer[0] != '\0')
        return TRUE;

    for ( i=0; i<elements(extensions); i++) {
        strcpy(temp, name);
        strcat(temp, extensions[i]);
        _searchenv(temp, "PATH", buffer);
        if ( buffer[0] != '\0')
            return TRUE;
    }

    return FALSE;
}


static HANDLE DetachProcess(char const *name, HANDLE const *streams) {
    STARTUPINFO s;
    PROCESS_INFORMATION p;
    char buffer[FILENAME_MAX];

    memset(&s, 0, sizeof s);
    s.cb = sizeof(s);
    s.dwFlags = STARTF_USESTDHANDLES;
    s.hStdInput = streams[0];
    s.hStdOutput = streams[1];
    s.hStdError = streams[2];

    if ( !find_image(name, buffer)) {
        system_error("Finding Image file");
        return INVALID_HANDLE_VALUE;
    }

// Since we've redirected the standard input, output and error handles
// of the child process, we create it without a console of its own.
// (That's the `DETACHED_PROCESS' part of the call.)  Other
// possibilities include passing 0 so the child inherits our console,
// or passing CREATE_NEW_CONSOLE so the child gets a console of its
// own.
//
    if (!CreateProcess(
        NULL,
        buffer, NULL, NULL,
        TRUE,
        DETACHED_PROCESS,
        NULL, NULL,
        &s,
        &p))
    {
        system_error("Spawning program");
        return INVALID_HANDLE_VALUE;
    }

// Since we don't need the handle to the child's thread, close it to
// save some resources.
    CloseHandle(p.hThread);

    return p.hProcess;
}

static HANDLE StartStreamHandler(ThrdProc proc, HANDLE stream) {

    DWORD ignore;

    return CreateThread(
        NULL,
        0,
        proc,
        (void *)stream,
        0,
        &ignore);
}

HANDLE CreateDetachedProcess(char const *name, stream_info *streams) {
// This Creates a detached process.
// First parameter: name of process to start.
// Second parameter: names of files to redirect the standard input, output and error 
//  streams of the child to (in that order.)  Any file name that is NULL will be 
//  redirected to an anonymous pipe connected to the parent.
// Third Parameter: handles of the anonymous pipe(s) for the standard input, output
// and/or error streams of the new child process.
//
// Return value: a handle to the newly created process.
//

    HANDLE child_handles[3];
    HANDLE process;

    int i;

// First handle the child's standard input.  This is separate from the 
// standard output and standard error because it's going the opposite 
// direction.  Basically, we create either a handle to a file the child
// will use, or else a pipe so the child can communicate with us.
// 
    if ( streams[0].filename != NULL ) {
        streams[0].handle = NULL;
        child_handles[0] = OpenInheritableFile(streams[0].filename);
    }
    else
        CreateInheritablePipe(child_handles, &(streams[0].handle), inherit_read);

// Now handle the child's standard output and standard error streams.  These
// are separate from the code above simply because they go in the opposite 
// direction.
//
    for ( i=1; i<3; i++) 
        if ( streams[i].filename != NULL) {
            streams[i].handle = NULL;
            child_handles[i] = CreateInheritableFile(streams[i].filename, APPEND);
        }
        else 
            CreateInheritablePipe(&(streams[i].handle), child_handles+i, inherit_write);

// Now that we've set up the pipes and/or files the child's going to use,
// we're ready to actually start up the child process:
    process = DetachProcess(name, child_handles);
    if (INVALID_HANDLE_VALUE == process)
        return process;

// Now that we've started the child, we close our handles to its ends of the pipes.
// If one or more of these happens to a handle to a file instead, it doesn't really 
// need to be closed, but it doesn't hurt either.  However, with the child's standard
// output and standard error streams, it's CRUCIAL to close our handles if either is a
// handle to a pipe.  The system detects the end of data on a pipe when ALL handles to
// the write end of the pipe are closed -- if we still have an open handle to the
// write end of one of these pipes, we won't be able to detect when the child is done
// writing to the pipe.
//
    for ( i=0; i<3; i++) {
        CloseHandle(child_handles[i]);
        if ( streams[i].handler ) 
            streams[i].handle = 
                StartStreamHandler(streams[i].handler, streams[i].handle);
    }
    return process;
}

#ifdef TEST

#define buf_size 256

unsigned long __stdcall handle_error(void *pipe) {
// The control (and only) function for a thread handling the standard
// error from the child process.  We'll handle it by displaying a
// message box each time we receive data on the standard error stream.
//
    char buffer[buf_size];
    HANDLE child_error_rd = (HANDLE)pipe;
    unsigned bytes;

    while (ERROR_BROKEN_PIPE != GetLastError() &&
        ReadFile(child_error_rd, buffer, 256, &bytes, NULL))
    {
        buffer[bytes+1] = '\0';
        MessageBox(NULL, buffer, "Error", MB_OK);
    }
    return 0;
}

unsigned long __stdcall handle_output(void *pipe) {
// A similar thread function to handle standard output from the child
// process.  Nothing special is done with the output - it's simply
// displayed in our console.  However, just for fun it opens a C high-
// level FILE * for the handle, and uses fgets to read it.  As
// expected, fgets detects the broken pipe as the end of the file.
//
    char buffer[buf_size];
    int handle;
    FILE *file;

    handle = _open_osfhandle((long)pipe, _O_RDONLY | _O_BINARY);
    file = _fdopen(handle, "r");

    if ( NULL == file )
        return 1;

    while ( fgets(buffer, buf_size, file))
        printf("%s", buffer);

    return 0;
}

int main(int argc, char **argv) {

    stream_info streams[3];
    HANDLE handles[3];
    int i;

    if ( argc < 3 ) {
        fputs("Usage: spawn prog datafile"
            "\nwhich will spawn `prog' with its standard input set to"
            "\nread from `datafile'.  Then `prog's standard output"
            "\nwill be captured and printed.  If `prog' writes to its"
            "\nstandard error, that output will be displayed in a"
            "\nMessageBox.\n",
                stderr);
        return 1;
    }

    memset(streams, 0, sizeof(streams));

    streams[0].filename = argv[2];
    streams[1].handler = handle_output;
    streams[2].handler = handle_error;

    handles[0] = CreateDetachedProcess(argv[1], streams);
    handles[1] = streams[1].handle;
    handles[2] = streams[2].handle;

    WaitForMultipleObjects(3, handles, TRUE, INFINITE);

    for ( i=0; i<3; i++)
        CloseHandle(handles[i]);

    return 0;
}

#endif


回答2:

As I understand you are using windows (because you mentioned process informaiton). If you want to get the launched process output, you must capture its output stream. Here's an explanatory link which shoes how it can be done.

my2c