0

我需要从 C++ 应用程序中运行一个外部程序。我需要该程序的输出(我想在程序仍在运行时查看它),它还需要获取输入。

重定向 IO 的最佳和最优雅的方式是什么?它应该在自己的线程中运行吗?有什么例子吗?

它在 OSX 上运行。

我是这样实现的:

ProgramHandler::ProgramHandler(std::string prog): program(prog){
// Create two pipes
std::cout << "Created Class\n";
pipe(pipe1);
pipe(pipe2);

int id = fork();

std::cout << "id: " << id << std::endl;

if (id == 0)
{
    // In child
    // Close current `stdin` and `stdout` file handles
    close(fileno(stdin));
    close(fileno(stdout));

    // Duplicate pipes as new `stdin` and `stdout`
    dup2(pipe1[0], fileno(stdin));
    dup2(pipe2[1], fileno(stdout));

    // We don't need the other ends of the pipes, so close them
    close(pipe1[1]);
    close(pipe2[0]);

    // Run the external program
    execl("/bin/ls", "bin/ls");
    char buffer[30];
    while (read(pipe1[0], buffer, 30)) {
        std::cout << "Buf: " << buffer << std::endl;
    }
}
else
{
    // We don't need the read-end of the first pipe (the childs `stdin`)
    // or the write-end of the second pipe (the childs `stdout`)
    close(pipe1[0]);
    close(pipe2[1]);


    // Now you can write to `pipe1[1]` and it will end up as `stdin` in the child
    // Read from `pipe2[0]` to read from the childs `stdout`
}

}

但作为输出我得到这个:

创建类

编号:84369

编号:0

我不明白为什么它被调用了两次,为什么它不会第一次分叉。我在做什么/理解错了。

4

4 回答 4

3

如果使用 POSIX 系统(如 OSX 或 Linux) ,那么您必须学习系统调用pipeforkclose和。dup2exec

您要做的是创建两个管道,一个用于从外部应用程序读取,一个用于写入。然后您fork创建一个新进程,并在子进程中将管道设置为stdinstdout然后调用它使用您的新和文件句柄exec将子进程替换为外部程序。在父进程中,您无法读取子进程的输出并写入其输入。stdinstdout

在伪代码中:

// Create two pipes
pipe(pipe1);
pipe(pipe2);

if (fork() == 0)
{
    // In child
    // Close current `stdin` and `stdout` file handles
    close(FILENO_STDIN);
    close(FILENO_STDOUT);

    // Duplicate pipes as new `stdin` and `stdout`
    dup2(pipe1[0], FILENO_STDIN);
    dup2(pipe2[1], FILENO_STDOUT);

    // We don't need the other ends of the pipes, so close them
    close(pipe1[1]);
    close(pipe2[0]);

    // Run the external program
    exec("/some/program", ...);
}
else
{
    // We don't need the read-end of the first pipe (the childs `stdin`)
    // or the write-end of the second pipe (the childs `stdout`)
    close(pipe1[0]);
    close(pipe2[1]);

    // Now you can write to `pipe1[1]` and it will end up as `stdin` in the child
    // Read from `pipe2[0]` to read from the childs `stdout`
}

阅读系统调用的手册页以获取有关它们的更多信息。您还需要添加错误检查,因为所有这些系统调用都可能失败。

于 2012-11-04T13:50:01.123 回答
1

好吧,有一种非常标准的方法可以做到这一点。通常,您希望分叉该进程并关闭子进程的标准 I/O (fd 0,1)。在分叉之前创建了两个管道,在分叉之后关闭子中的标准输入和输出并将它们连接到管道,使用 dup。

伪代码,仅显示连接的一侧,我相信您可以弄清楚另一侧。

int main(){
     int fd[2]; // file descriptors
     pipe(fd);

      // Fork child process
      if (fork() == 0){
            char buffer [80];
            close(1);
            dup(fd[1]); // this will take the first free discriptor, the one you just closed. 
            close(fd[1]); // clean up
        }else{
           close(0);
           dup(fd[0]);
           close(fd[0]);
        }    
        return 0;
    }

在您设置好管道并且其中一个父线程等待选择或其他内容后,您可以为您的外部工具调用 exec 并让所有数据流动。

于 2012-11-04T13:59:45.217 回答
0

在 POSIX 系统上与不同程序进行通信的基本方法是设置 a pipe(),然后fork()将您的程序close()dup()文件描述符设置到正确的位置,最后设置到exec??()所需的可执行文件。

完成此操作后,您就可以将两个程序与合适的流连接起来。不幸的是,这不涉及任何形式的两个程序的异步处理。也就是说,您可能希望使用合适的异步和非阻塞操作访问创建的文件描述符(即,将各种文件描述符设置为非阻塞和/或仅在poll()产生表明您可以的结果时访问它们访问它们)。但是,如果只有一个可执行文件,那么从单独的线程中控制它可能会更容易。

于 2012-11-04T13:57:26.103 回答
0

另一种方法(如果您也在编写外部程序)是使用共享内存。类似于(伪代码)的东西

// create shared memory
int l_shmid = shmget(key, size ,0600 | IPC_CREAT);

if(l_shmid < 0) 
  ERROR

// attach to shared memory
dataptr* ptr = (dataptr*)shmat(l_shmid, NULL, 0600);

// run external program
pid_t l_pid = fork();

if(l_pid == (pid_t)-1)
{
  ERROR

  // detach & delete shared mem
  shmdt(ptr);
  shmctl(l_shmid, 
         IPC_RMID,
         (shmid_ds *)NULL);
  return;
}
else if(l_pid == 0)
{
    // child: 
    execl(path,
          args,
          NULL); 
    return;
}

// wait for the external program to finish
int l_stat(0);
waitpid(l_pid, &l_stat, 0);

// read from shmem
memset(mydata, ..,..);
memcpy(mydata, ptr, ...);

// detach & close shared mem
shmdt(ptr);
shmctl(l_shmid, 
       IPC_RMID,
       (shmid_ds *)NULL);

您的外部程序可以以类似的方式写入共享内存。无需管道和读/写/选择等。

于 2013-01-14T17:26:34.287 回答