سؤال

في لغة C باستخدام Windows API، كيف يمكنني الحصول على مخرجات العملية عندما يكون لدي معلومات العملية الخاصة بها؟

لدي رمز مثل هذا:

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 بطباعة مخرجات معينة، وأريد نقلها إلى عمليتي (التي استخدمت فيها الكود أعلاه).

هل كانت مفيدة؟

المحلول

من المحتمل أن تكون هذه الإجابة أطول قليلاً مما كنت تتوقعه، ولكن هذه هي الطريقة التي تكون بها واجهة برمجة تطبيقات Windows في بعض الأحيان.على أية حال، على الرغم من أن هذا يتطلب تعليمات برمجية أكثر مما يبدو في البداية أنه يحتاج إليه، إلا أن هذا على الأقل يوفر واجهة نظيفة وسهلة الاستخدام إلى حد ما للبرامج التي تريد القيام بأشياء مثل هذه.يتم التعليق على الكود بشكل حر إلى حد ما، موضحًا ليس فقط كيفية استخدام الوظيفة التي يوفرها، ولكن أيضًا كيف يفعل معظم ما يفعله، وما يتطلبه 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

نصائح أخرى

كما أفهم أنك تستخدم Windows (لأنك ذكرت معلومات العملية).إذا كنت ترغب في الحصول على إخراج العملية المطلق، فيجب عليك التقاط دفق الإخراج.هنا رابط توضيحي الأحذية التييمكن القيام به.

my2c

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top