如何启动流程并获取其输出?

时间:2011-03-30 13:13:02

标签: c winapi process createprocess

在使用Windows API的C语言中,如何在获取进程信息时获取进程的输出?

我有这样的代码:

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

并且进程asd.exe打印出某个输出,我想把它带到我的进程(我使用上面代码的那个)。

2 个答案:

答案 0 :(得分:4)

这个答案可能比您预期的要长一些,但这就是Windows API的有时候。在任何情况下,即使这需要比最初看起来需要的代码更多的代码,这至少为想要做这样的事情的程序提供了一个相当干净,易于使用的界面。代码被公平地评论,不仅解释了如何使用它提供的功能,还解释了它如何使用它所做的大部分工作,以及当你决定基于此而不是直接使用它来编写代码时Windows需要什么。

我还应该指出,这是一些相当古老的代码。它运行得很好,我没有任何理由重写它,但如果我今天再次这样做,我很确定我会做的有点不同(一方面,我毫无疑问会使用C ++而不是直接C,就像我在这里所做的那样。)

这也包含一些经常用于完全不相关的代码的代码(例如,我使用system_error相当多的地方 - 它只是Windows,但实际上是产生子进程的偶然事件)。

无论如何,我们将从spawn.h开始,它定义了代码的接口:

#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

然后执行CreateDetachedProcess(以及一些测试/演示代码):

#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

答案 1 :(得分:1)

据我所知,你正在使用windows(因为你提到过程信息)。如果要获取已启动的进程输出,则必须捕获其输出流。这是an explanatory link,它可以说明如何做到这一点。

MY2C