I've managed to port some code from msdn to Mi

2019-06-07 00:41发布

问题:

The code, sory it is abit too long but I've managed it to shorten it only to such size, the key issue is (I think) with this strange for loop at the end. No, I don't know why the loop header is empty, microsoft want's it that way.

The problem is that the code waits to eternity for yet more data from child app.

The page with full algorighm: http://msdn.microsoft.com/en-us/library/ms682499(VS.85).aspx

(Yes, I know it's a mess, but it is self sustained mess at least.)

#include <iostream>
#include <stdio.h>
#include <windows.h>

using namespace std;

#define BUFSIZE 4096 

int main() { 
  SECURITY_ATTRIBUTES saAttr; 
  printf("\n->Start of parent execution.\n");
  // Set the bInheritHandle flag so pipe handles are inherited.
  saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
  saAttr.bInheritHandle = TRUE;
  saAttr.lpSecurityDescriptor = NULL;
  // Create a pipe for the child process's STDOUT.
  HANDLE g_hChildStd_OUT_Rd = NULL;
  HANDLE g_hChildStd_OUT_Wr = NULL;
  CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0);
  // Ensure the read handle to the pipe for STDOUT is not inherited.
  SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0);
  // Create a pipe for the child process's STDIN.
  HANDLE g_hChildStd_IN_Rd = NULL;
  HANDLE g_hChildStd_IN_Wr = NULL;
  CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0);
  // Ensure the write handle to the pipe for STDIN is not inherited.
  SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0);
  // Create the child process.
  // Create a child process that uses the previously created pipes for STDIN and STDOUT.
  char szCmdline[]="cmd /c dir";
  PROCESS_INFORMATION piProcInfo;
  STARTUPINFO siStartInfo;
  BOOL bCreateSuccess = 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 STDIN and STDOUT handles for redirection.
  ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
  siStartInfo.cb = sizeof(STARTUPINFO); 
  siStartInfo.hStdError = g_hChildStd_OUT_Wr;
  siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
  siStartInfo.hStdInput = g_hChildStd_IN_Rd;
  siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
  // Create the child process.
  bCreateSuccess = 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

  DWORD dwRead, dwWritten; 
  CHAR chBuf[BUFSIZE];
  BOOL bWriteSuccess = FALSE;

  BOOL bReadSuccess = FALSE;
  HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
  for (;;) {
    bReadSuccess = ReadFile( g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
    if( ! bReadSuccess || dwRead == 0 ) break; 
    bReadSuccess = WriteFile(hParentStdOut, chBuf, dwRead, &dwWritten, NULL);
    if (! bReadSuccess ) break;
  }

  printf("\n->End of parent execution.\n");

  return 0; 
}

回答1:

From the looks of things, you've forgotten to close the parent's handles to the write-end of the pipes you're passing to the child process. Since there's still a valid write handle to the pipe, the system can't detect that writing to the pipe is no longer possible, and you'll wait infinitely for the child to finish.

If you only need to capture the child's standard output, _popen may be a lot easier way to do it.

Edit: Okay, some ancient code to spawn a child process with all three of its standard streams directed to pipes that connect to the parent. This is a lot longer than it should be for such a simple task, but such is life with the Windows API. To be fair, it probably could be shorter, but it's 20 years old (or so). Neither the API nor the way I wrote code then is quite what it is now (though some might not consider my newer code any improvement).

#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