c++ - Getting the output of Tcl Interpreter -


i trying output of tcl interpreter described in answer of question tcl c api: redirect stdout of embedded tcl interp file without affecting whole program. instead of writing data file need using pipe. changed tcl_openfilechannel tcl_makefilechannel , passed write-end of pipe it. called tcl_eval puts. no data came @ read-end of pipe.

#include <sys/wait.h> #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h> #include <tcl.h> #include <iostream>  int main() {     int pfd[2];     if (pipe(pfd) == -1) { perror("pipe"); exit(exit_failure); } /*         int saved_flags = fcntl(pfd[0], f_getfl);         fcntl(pfd[0], f_setfl, saved_flags | o_nonblock); */          tcl_interp *interp = tcl_createinterp();          tcl_channel chan;         int rc;         int fd;          /* channel bound stdout.          * initialize standard channels byproduct          * if wasn't done. */         chan = tcl_getchannel(interp, "stdout", null);         if (chan == null) {                 return tcl_error;         }          /* duplicate descriptor used stdout. */         fd = dup(1);         if (fd == -1) {                 perror("failed duplicate stdout");                 return tcl_error;         }          /* close stdout channel.          * byproduct, closes fd 1, we've cloned. */         rc = tcl_unregisterchannel(interp, chan);         if (rc != tcl_ok)                 return rc;          /* duplicate our saved stdout descriptor back.          * dup() semantics such if doesn't fail,          * fd 1 back. */         rc = dup(fd);         if (rc == -1) {                 perror("failed reopen stdout");                 return tcl_error;         }          /* rid of cloned fd. */         rc = close(fd);         if (rc == -1) {                 perror("failed close cloned fd");                 return tcl_error;         }          chan = tcl_makefilechannel((void*)pfd[1], tcl_writable | tcl_readable);         if (chan == null)                 return tcl_error;          /* since stdout channel not exist in interp,          * call make our file channel new stdout. */         tcl_registerchannel(interp, chan);            rc = tcl_eval(interp, "puts test");         if (rc != tcl_ok) {                 fputs("failed eval", stderr);                 return 2;         }          char buf;         while (read(pfd[0], &buf, 1) > 0) {             std::cout << buf;         }  } 

i've no time @ moment tinker code (might later) think approach flawed see 2 problems it:

  1. if stdout connected not interactive console (a call isatty(2) employed runtime check that), full buffering (and think be) engaged, unless call puts in embedded interpreter outputs many bytes fill or overflow tcl's channel buffer (8kib, istr) , downstream system's buffer (see next point), which, think, won't less 4kib (the size of single memory page on typical hw platform), nothing come @ read side.

    you test changing tcl script flush stdout, this:

    puts 1 flush stdout puts 2 

    you should able read 4 bytes output first puts pipe's read end.

  2. a pipe 2 fds connected via buffer (of defined system-dependent size). write side (your tcl interp) fills buffer, write call hit "buffer full" condition block writing process unless reads read end free space in buffer. since reader the same process, such condition has perfect chance deadlock since tcl interp stuck trying write stdout, whole process stuck.

now question is: made working?

the first problem might partially fixed turning off buffering channel on tcl side. (supposedly) won't affect buffering provided pipe system.

the second problem harder, , can think of 2 possibilities fix it:

  1. create pipe fork(2) child process ensuring standard output stream connected pipe's write end. embed tcl interpreter in process , nothing stdout stream in implicitly connected child process standard output stream attached, in turn, pipe. read in parent process pipe until write side closed.

    this approach more robust using threads (see next point) has 1 potential downside: if need somehow affect embedded tcl interpreter in ways not known front before program run (say, in response user's actions), have set sort of ipc between parent , child processes.

  2. use threading , embed tcl interp separate thread: ensure reads pipe happen in another (let's call "controlling") thread.

    this approach might superficially simpler forking process hassles related proper synchronization common threading. instance, tcl interpreter must not accessed directly threads other 1 in interp created. implies not concurrent access (which kind of obvious itself) access @ all, including synchronized, because of possible tls issues. (i'm not sure holds true, have feeling big can of worms.)

so, having said that, wonder why seem systematically reject suggestions implement custom "channel driver" interp , use provide implementation stdout channel in interp? create super-simple single-thread fully-synchronized implementation. what's wrong approach, really?

also observe if decided use pipe in hope serve sort of "anonymous file", wrong: pipe assumes both sides work in parallel. , in code first make tcl interp write has write , try read this. asking trouble, i've described, if invented not mess file, you're doing wrong, , on posix system course of actions be:

  1. use mkstemp() create , open temporary file.
  2. immediately delete using name mkstemp() returned in place of template passed it.

    since file still has open fd (returned mkstemp()), disappear file system not unlinked, , might written , read from.

  3. make fd interp's stdout. let interp write has to.
  4. after interp finished, seek() fd beginning of file , read it.
  5. close fd when done — space occupied on underlying filesystem reclamied.

Comments