1
votes

I'm working on a C program in Xcode on OSX.

The (parent) program has to launch a new (child) process which receives its input via stdin and outputs results to stdout. So the parent writes data to the child process's stdin and the parent reads results from the child process's stdout.

On Windows I use CreateProcess to do the above, but I'm not sure how it's done on OSX in C.

I believe I'm supposed to use exec to start the process, but I don't see how I redirect stdin and stdout of the executable (child process) which exec starts. And from reading the manual it also looks like the child process will become the parent process if I use exec. The child and parent process has to run in parallel so that the parent process can write and read to the child process when it needs to.

Is there a kind OSX C expert out there who could give me a brief example of how the above is done?

Thanks

EDIT

I think I understand. But if the child process is an infinite while-loop which waits for input on stdin, then it won't turn into a "zombie", right?

The child process basically does this:

1. Read data from stdin (i.e. blocked until data is received)
2. Process data
3. Write result to stdout
4. Goto 1

After I read your post, I found this page:

http://www.jukie.net/~bart/snippets/popenRWE/popenRWE.c.html

However, I'm having a problem getting my .exe (child process) to launch

In a terminal, I would start the .exe like this:

./myApp.exe someParam1 someParam2 someParam3

The API looks like this:

popenRWE(int *rwepipe, const char *exe, const char *const argv[])

I'm guessing that the second argument should be:

 const char* exe = "./myApp.exe";

and that the third argument should be:

 char* p0 = "./myApp.exe";
 char* p1 = "someParam1";
 char* p2 = "someParam2";
 char* p3 = "someParam3";

 char** argv[4] = {p0, p1,p2,p3};

Am I right?

1
1. As long as your child doesn't terminate, then it won't become a zombie, that's correct. But: you have to clean up (i.e. wait for) your child process when you're parent process terminates. Otherwise the child process will become the child of the launch process and you won't have any means of terminating it (in a nice way).Max Leske
2. the arguments look right to me (although I'm not familiar with Windows specifics. On Unix that should work). One thing though: launching would look more like this: <popenRWE program> ./myApp.exe someParam1 (otherwise you would run the exe directly).Max Leske
Can you clarify that? What do you mean by <popenRWE program> ./myApp.exe someParam1 ? Are you saying that the variables exe, p0, p1, p2 and p3 are set incorrectly above? If so, what should they look like?user1884325
Here's what I understood: your program myApp.exe is the one you want to run as child process and which will listen for input. If that is true then running ./myApp.exe someParam1 will start myApp.exe directly as a sub-process of the shell and the whole point of implementing pipe / fork / exec becomes moot. From the sources you have provided I gather, that there is a second program, let's call it <popenRWE program>, which implements popenRWE() and takes the name of your app as an argument. To actually make use of that second program you'd have to run <popenRWE program> ./myApp.exeMax Leske
Ahhh...gotcha...Except it's done programatically. The main program doesn't use the input args from main. Thank you for all your feedback. It's been very useful and I really appreciate it.user1884325

1 Answers

3
votes

I'm including the source of a small library I've written a while ago. That should get you started. Fork / pipe / exec isn't really that easy (especially with all the variants of exec) and it took me a while too. So here goes:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include "limbo.h"

int out_pipe[2], err_pipe[2];

int run_command(char *argv[], int *out_length, int *err_length){
    pid_t pid;
    int status = 0;
    struct stat out_stat, err_stat;

    pipe(out_pipe); //create a pipe
    pipe(err_pipe);
    if(!(pid = fork())) //spawn child 
    {
        // Child. Close the read end of the pipe
        close(out_pipe[0]);
        close(err_pipe[0]);
        // redirect stdout and stderr to the write end of the pipe
        dup2(out_pipe[1], STDOUT_FILENO);
        dup2(err_pipe[1], STDERR_FILENO);
        status = execv(argv[0], argv); //child will terminate here
    }

    //Only parent gets here. Close write end of the pipe
    close(out_pipe[1]);
    close(err_pipe[1]);
    //or wait for the child process to terminate
    waitpid(pid, &status, 0);

    fstat(out_pipe[0], &out_stat);
    fstat(err_pipe[0], &err_stat);
    
    *out_length = (int) out_stat.st_size;
    *err_length = (int) err_stat.st_size;
    
    return status;
}

int read_buffers(char *out_buffer, int out_length, char *err_buffer, int err_length){
    out_buffer[read(out_pipe[0], out_buffer, out_length)] = 0;
    err_buffer[read(err_pipe[0], err_buffer, err_length)] = 0;
    
    return 0;
}

The comments in the code should help you to understand the code. Feel free to reuse.

Edit

In response to your comment:

The waitpid() call makes the parent process wait for the termination of the child process. If you want both processes to run in parallel, you need to get rid of waitpid() in the place that I use it. But be careful: without a call to one of the wait functions your child process will become a zombie once it finishes. It is your responsibility to keep an eye on your child process and to wait for it so the process can be cleaned up by the kernel.