سؤال

I have an exe program in Windows which in the terminal works as follows

> program.exe parameter01 file
entry01 (user types entry01)
output01
entry02 (user types entry02)
output02
...
until the combination Ctrl+D is pressed.

I need to create a "child process" in a C language which is capable to run the program and sent entries to the "child process" and receive the output in a char[] or string.

I know I have to use the CreateProcess method but I don't know how to pass an entry like an input and retrieve the output, How can I do this?

I have seen this using Java but I need to implement this functionality in C language.

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

المحلول

You can try creating a Child Process with Redirected Input and Output, I adapted the code found here

#include <windows.h> 
#include <tchar.h>
#include <stdio.h> 
#include <strsafe.h>

#define BUFSIZE 4096 

/* child process's STDIN is the user input or data that you enter into the child process - READ */
HANDLE g_hChildStd_IN_Rd = NULL;
/* child process's STDIN is the user input or data that you enter into the child process - WRITE */
HANDLE g_hChildStd_IN_Wr = NULL;
/* child process's STDOUT is the program output or data that child process returns - READ */
HANDLE g_hChildStd_OUT_Rd = NULL;
/* child process's STDOUT is the program output or data that child process returns - WRITE */
HANDLE g_hChildStd_OUT_Wr = NULL;

void CreateChildProcess(void);
void WriteToPipe(CHAR chBuf[]);
void ReadFromPipe(void);
void ErrorExit(PTSTR);

int _tmain(int argc, TCHAR *argv[])
{
    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;

    //child process's STDOUT is the program output or data that child process returns
    // Create a pipe for the child process's STDOUT. 
    if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
        ErrorExit(TEXT("StdoutRd CreatePipe"));

    // Ensure the read handle to the pipe for STDOUT is not inherited.
    if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
        ErrorExit(TEXT("Stdout SetHandleInformation"));

    //child process's STDIN is the user input or data that you enter into the child process
    // Create a pipe for the child process's STDIN. 
    if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
        ErrorExit(TEXT("Stdin CreatePipe"));

    // Ensure the write handle to the pipe for STDIN is not inherited. 
    if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
        ErrorExit(TEXT("Stdin SetHandleInformation"));
    // Create the child process. 

    CreateChildProcess();

    /* variables */
    char FAR *lpsz;
    int cch;

    CHAR chBuf[BUFSIZE];
    DWORD dwRead = strlen(chBuf);
    HANDLE hStdin;
    BOOL bSuccess;

    hStdin = GetStdHandle(STD_INPUT_HANDLE);
    if (hStdin == INVALID_HANDLE_VALUE)
        ExitProcess(1);

    for (;;)
    {
        // Read from standard input and stop on error or no data.
        bSuccess = ReadFile(hStdin, chBuf, BUFSIZE, &dwRead, NULL);

        if (!bSuccess || dwRead == 0)
            break;

        lpsz = &chBuf[0];

        // Write to the pipe that is the standard input for a child process. 
        // Data is written to the pipe's buffers, so it is not necessary to wait
        // until the child process is running before writing data.
        WriteToPipe(lpsz);
        printf("\n->Contents of %s written to child STDIN pipe.\n", argv[1]);
        // Read from pipe that is the standard output for child process. 
        printf("\n->Contents of child process STDOUT:\n\n", argv[1]);
        ReadFromPipe();
        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;
}

void CreateChildProcess()
// Create a child process that uses the previously created pipes for STDIN and STDOUT.
{
    TCHAR szCmdline[] = TEXT("cmd.exe /c \"C:\\path\\to\\exe\\program.exe -parameter C:\\path\\to\\file\\file.txt\"");
    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 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. 

    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 

    // If an error occurs, exit the application. 
    if (!bSuccess)
        ErrorExit(TEXT("CreateProcess"));
    else
    {
        // Close handles to the child process and its primary thread.
        // Some applications might keep these handles to monitor the status
        // of the child process, for example. 
        CloseHandle(piProcInfo.hProcess);
        CloseHandle(piProcInfo.hThread);
    }
}

void WriteToPipe(CHAR chBuf[])
// Read from a file and write its contents to the pipe for the child's STDIN.
// Stop when there is no more data. 
{
    DWORD dwRead, dwWritten;
    // CHAR chBuf[] = "hola\n";
    dwRead = strlen(chBuf);
    BOOL bSuccess = FALSE;
    bSuccess = WriteFile(g_hChildStd_IN_Wr, chBuf, dwRead, &dwWritten, NULL);
    if (!bSuccess) ErrorExit(TEXT("StdInWr Cannot write into child process."));
    /*
    // Close the pipe handle so the child process stops reading. 
    if (!CloseHandle(g_hChildStd_IN_Wr))
        ErrorExit(TEXT("StdInWr CloseHandle"));
    */
}

void ReadFromPipe(void)
// 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. 
{
    DWORD dwRead, dwWritten;
    CHAR chBuf[BUFSIZE];
    BOOL bSuccess = FALSE;
    HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    WORD wResult = 0;
    bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
    if (!bSuccess || dwRead == 0) ErrorExit(TEXT("StdOutRd Cannot read child process's output."));
    if (chBuf[0] == '+' && chBuf[1] == '?') { printf("It's misspelled."); }
    else { printf("It's spelled correctly."); }
    // bSuccess = WriteFile(hParentStdOut, chBuf, dwRead, &dwWritten, NULL);
    // if (!bSuccess) ErrorExit(TEXT("StdOutWr Cannot write into parent process's output."));
}

void ErrorExit(PTSTR lpszFunction)
// Format a readable error message, display a message box, 
// and exit from the application.
{
    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dw = GetLastError();

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf,
        0, NULL);

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
        (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40)*sizeof(TCHAR));
    StringCchPrintf((LPTSTR)lpDisplayBuf,
        LocalSize(lpDisplayBuf) / sizeof(TCHAR),
        TEXT("%s failed with error %d: %s"),
        lpszFunction, dw, lpMsgBuf);
    MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
    ExitProcess(1);
}

نصائح أخرى

Use the STARTUPINFO structure

You must set the attribute hStdInput.

More and less, this is what you need (it is C++ code and it may not compile, but you'll get the idea):

std::string GetProcessOutput(HANDLE hStdOutProcess) {
    std::stringstream strStream;
    char lpBuffer[2] = {0};
    DWORD nBytesRead;
    while(true){
        BOOL bResult = ReadFile(hStdOutProcess, lpBuffer, sizeof(char), &nBytesRead, NULL);
        if (bResult && nBytesRead) {
            strStream << lpBuffer;
        } else {
            break;
        }
    }
    return strStream.str();
}

void RunAndGetOutout() {
    HANDLE hProcessStdOutRead = NULL;
    HANDLE hProcessStdOutWrite = NULL;

    SECURITY_ATTRIBUTES saAttr;
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
    saAttr.bInheritHandle = TRUE;  // allow to inherit handles from parent process
    saAttr.lpSecurityDescriptor = NULL; 

    if(!CreatePipe(&hProcessStdOutRead, &hProcessStdOutWrite, &saAttr, 0)) {
        return nResult;
    }
    if(!SetHandleInformation(hProcessStdOutRead, HANDLE_FLAG_INHERIT, 0)) {
        return nResult;
    }

    STARTUPINFO startInfo;
    PROCESS_INFORMATION processInfo;
    char cmdLine[ MAX_PATH*2 +40] = {0};
    char currentDir[MAX_PATH] = {0};

    ZeroMemory(&startInfo, sizeof(startInfo));
    startInfo.cb = sizeof(startInfo);
    startInfo.hStdOutput = hProcessStdOutWrite; // set the handle
    startInfo.dwFlags |= STARTF_USESTDHANDLES; // attention with this one

    ZeroMemory(&processInfo, sizeof(processInfo));

    GetCurrentDirectory(MAX_PATH, currentDir);
    sprintf(cmdLine, "\"%s\" %s", (const char*)m_path2Process, (const char*)m_processArgs);

    if(CreateProcess( NULL, cmdLine, NULL, NULL, TRUE, 0, NULL, currentDir, &startInfo, &processInfo)) {

        cout << GetProcessOutput(hProcessStdOutRead) << endl;

        CloseHandle(processInfo.hThread);
        CloseHandle(processInfo.hProcess);
    }

    CloseHandle(hProcessStdOutRead);
    CloseHandle(hProcessStdOutWrite)
}

Basically you will need to create an inter-process communication system on a Win32 platform.

You can do it in a few different ways: pipes, shared memory, IPC, WinSock, DDE...

All these features compete to offer you the crappiest possible API, with truckloads of inconsistent and useless parameters, unstandardized return codes and incoherent function names. And the bloody awkward circa 1995 semi-unicode handling, on top of that.

Here is an example with a named pipe.

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

// name of our glorious pipe
#define PIPE_NAME L"\\\\.\\pipe\\whatever" // bloody unicode string

// exit on fatal error
void panic(const char * msg)
{
    fprintf(stderr, "***PANIC*** %s\n", msg);
    exit(-1);
}

// father process
void father(const char * own_name) // name of our own executable to launch a copy of ourselve
{
    printf("Father process starting\n");

    // create a monodirectional father->child named pipe
    HANDLE pipe = CreateNamedPipe (
        PIPE_NAME,            // name of the pipe
        PIPE_ACCESS_OUTBOUND, // send only
        PIPE_TYPE_BYTE,       // send data as a byte stream
        1,                    // only one instance
        0, 0, 0, NULL);       // default junk
    if (pipe == INVALID_HANDLE_VALUE) panic("could not create pipe");

    // spawn child process
    {
        STARTUPINFOA si;
        PROCESS_INFORMATION pi;

        ZeroMemory(&si, sizeof(si));
        si.cb = sizeof(si);
        ZeroMemory(&pi, sizeof(pi));
        if (!CreateProcessA(    // using ASCII variant to be compatible with argv
            own_name,           // executable name (ourself)
            "child",            // command line. This will be seen as argv[0]
            NULL, NULL, FALSE,  // default junk
            CREATE_NEW_CONSOLE, // launch in another console window
            NULL, NULL,         // more junk
            &si, &pi))          // final useless junk
            panic("could not create child process");
    }

    // connect to child process
    BOOL result = ConnectNamedPipe(pipe, NULL);
    if (!result) panic("could not connect to child process");

    // talk to child
    for (;;)
    {
        // read an input line
        char line[100];
        printf("Say something >");
        if (fgets(line, sizeof(line), stdin) == NULL)
            panic("could not read from standard input");

        // exit on an empty line
        if (!strcmp(line, "\n")) break;

        // send the line to the child
        DWORD written = 0;
        if (!WriteFile(
            pipe,
            line,         // sent data
            strlen(line), // data length
            &written,     // bytes actually written
            NULL))
            panic("could not write to pipe");
    }

    // close the pipe
    CloseHandle(pipe);
}


void child(void)
{
    printf("Child process starting\n");

    // retrieve communication pipe
    HANDLE pipe = CreateFile(
         PIPE_NAME,      // name of the pipe
         GENERIC_READ,   // read ONLY access (or else the call will fail) 
         0, NULL,        // junk
         OPEN_EXISTING,  // opens existing pipe 
         0, NULL);       // more junk 
    if (pipe == INVALID_HANDLE_VALUE) panic("could not connect to the pipe");

    // read father's input
    for (;;)
    {
        char buffer[80];
        DWORD read = 0;
        if (!ReadFile(
                pipe,
                buffer,           // read data
                sizeof(buffer)-1, // max length (leave room for terminator)
                &read,            // bytes actually read
                NULL))
            break; // exit if the pipe has closed

        // display what our father said
        buffer[read] = '\0'; // make sure what we just read will be displayable as a string
        printf("Father said: %s", buffer);
    }

    // close pipe
    CloseHandle(pipe);
}

int main(int argc, char *argv[])
{
    // wait for a <return> keypress on exit
    atexit(getchar);

    // decide whether we are the father or the child
    if (!strcmp (argv[0], "child")) child();
    else                            father(argv[0]);

    printf("Done\n");
    return 0;
}

Sorry, I could not find a graceful way of using Ctrl-D as an exit signal. The only way I could think of achieving this would have required yet another couple of extremely tedious system calls, and I was daunted by the prospect.

So the father will terminate when the user enters an empty line.

By closing the pipe, the father will trigger a read error that will put the child out of its misery too (i.e. the child breaks out of the reading loop and dies when it gets a read error on the pipe). You can easily have the child react to whatever message instead, if that can please your teacher better.

I have added an extra wait for another keypress in case you're running this from an IDE, to avoid closing the windows too abruptly. Just remove the atexit at the start of main() if you don't want that.

A simple example:

#include <signal.h> 

void SigQuit_Handle(int sig){
   exit(1);
}
int main(int argc, char *argv[]){
  char buffer[1024];
  signal( SIGQUIT, SigQuit_Handle );
  signal( SIGINT,  SIG_IGN ); // If you want to ignore Ctrl + C
  while ( true ){
   fgets(buffer, sizeof(buffer), INPUT_BUFFER);
  } 
  return 0;
}

Edit: In case of handling threads, you may need include sys/types.h.

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