C++ 获取Tcl解释器的输出

C++ 获取Tcl解释器的输出,c++,tcl,C++,Tcl,我正在尝试获取Tcl解释器的输出,如回答此问题时所述。我需要使用管道获取数据,而不是将数据写入文件。我将Tcl\u OpenFileChannel更改为Tcl\u MakeFileChannel,并将管道的写入端传递给它。然后我用一些put调用了Tcl\u Eval。在管道的读取端并没有数据 #include <sys/wait.h> #include <assert.h> #include <stdio.h> #include <stdlib.h>

我正在尝试获取Tcl解释器的输出,如回答此问题时所述。我需要使用管道获取数据,而不是将数据写入文件。我将
Tcl\u OpenFileChannel
更改为
Tcl\u MakeFileChannel
,并将管道的写入端传递给它。然后我用一些put调用了
Tcl\u Eval
。在管道的读取端并没有数据

#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;

        /* Get the channel bound to stdout.
         * Initialize the standard channels as a byproduct
         * if this wasn't already done. */
        chan = Tcl_GetChannel(interp, "stdout", NULL);
        if (chan == NULL) {
                return TCL_ERROR;
        }

        /* Duplicate the descriptor used for stdout. */
        fd = dup(1);
        if (fd == -1) {
                perror("Failed to duplicate stdout");
                return TCL_ERROR;
        }

        /* Close stdout channel.
         * As a byproduct, this closes the FD 1, we've just cloned. */
        rc = Tcl_UnregisterChannel(interp, chan);
        if (rc != TCL_OK)
                return rc;

        /* Duplicate our saved stdout descriptor back.
         * dup() semantics are such that if it doesn't fail,
         * we get FD 1 back. */
        rc = dup(fd);
        if (rc == -1) {
                perror("Failed to reopen stdout");
                return TCL_ERROR;
        }

        /* Get rid of the cloned FD. */
        rc = close(fd);
        if (rc == -1) {
                perror("Failed to close the cloned FD");
                return TCL_ERROR;
        }

        chan = Tcl_MakeFileChannel((void*)pfd[1], TCL_WRITABLE | TCL_READABLE);
        if (chan == NULL)
                return TCL_ERROR;

        /* Since stdout channel does not exist in the interp,
         * this call will make our file channel the new stdout. */
        Tcl_RegisterChannel(interp, chan);



        rc = Tcl_Eval(interp, "puts test");
        if (rc != TCL_OK) {
                fputs("Failed to eval", stderr);
                return 2;
        }

        char buf;
        while (read(pfd[0], &buf, 1) > 0) {
            std::cout << buf;
        }

}
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
int main(){
int-pfd[2];
如果(管道(pfd)=-1){perror(“管道”);出口(出口故障)}
/*
int saved_flags=fcntl(pfd[0],F_GETFL);
fcntl(pfd[0],F_设置FL,保存的_标志| O_非块);
*/
Tcl_Interp*Interp=Tcl_CreateInterp();
Tcl_chan频道;
int rc;
int-fd;
/*将频道绑定到标准输出。
*将标准通道初始化为副产品
*如果这还没有完成*/
chan=Tcl_GetChannel(interp,“stdout”,NULL);
如果(chan==NULL){
返回TCL_错误;
}
/*复制用于标准输出的描述符*/
fd=dup(1);
如果(fd==-1){
perror(“未能复制标准输出”);
返回TCL_错误;
}
/*关闭标准输出通道。
*作为副产品,这关闭了FD1,我们刚刚克隆*/
rc=Tcl_注销通道(interp,chan);
如果(rc!=TCL\u正常)
返回rc;
/*将保存的标准输出描述符复制回来。
*dup()语义是这样的,如果它没有失败,
*我们找回FD1*/
rc=dup(fd);
如果(rc==-1){
perror(“未能重新打开标准输出”);
返回TCL_错误;
}
/*摆脱克隆的FD*/
rc=关闭(fd);
如果(rc==-1){
perror(“未能关闭克隆的FD”);
返回TCL_错误;
}
chan=Tcl_MakeFileChannel((void*)pfd[1],Tcl_可写| Tcl_可读);
如果(chan==NULL)
返回TCL_错误;
/*由于interp中不存在标准输出通道,
*这个调用将使我们的文件通道成为新的标准输出*/
Tcl_注册频道(interp,chan);
rc=Tcl_Eval(interp,“puts测试”);
如果(rc!=TCL\u正常){
fputs(“评估失败”,标准);
返回2;
}
焦炉;
而(读取(pfd[0],&buf,1)>0){

std::cout我现在没有时间修改代码(以后可能会这样做),但我认为这种方法有缺陷,因为我发现它有两个问题:

  • 如果
    stdout
    连接到非交互式控制台的东西(运行时通常使用调用
    isatty(2)
    来检查这一点),则可以(而且我认为将会)使用完全缓冲因此,除非调用
    放入嵌入式解释器,否则将输出大量字节,以填充或溢出Tcl的通道缓冲区(8KiB,ISTR),然后填充或溢出下游系统的缓冲区(参见下一点),我认为,该缓冲区不会小于4KiB(典型硬件平台上单个内存页的大小),读取端将不会出现任何问题

    您可以通过将Tcl脚本更改为flush
    stdout
    来测试这一点,如下所示:

    puts one
    flush stdout
    puts two
    
    然后,您应该能够从管道的读取端读取第一个
    put
    输出的四个字节

  • 管道是通过缓冲区连接的两个FD(具有已定义但依赖于系统的大小)。一旦写入端(Tcl interp)填满该缓冲区,写入调用将命中“缓冲区已满”条件将阻止写入过程,除非有东西从读取端读取以释放缓冲区中的空间。由于读卡器是同一个进程,这样的条件很有可能会死锁,因为一旦Tcl interp尝试写入stdout
  • ,整个过程就会死锁

    现在的问题是:这能起作用吗

    第一个问题可以通过在Tcl端关闭该通道的缓冲来部分解决。这(假定)不会影响系统为管道提供的缓冲

    第二个问题更难,我只能想到两种解决方法:

  • 创建一个管道,然后
    fork(2)
    确保其标准输出流连接到管道写入端的子进程。然后在该进程中嵌入Tcl解释器,不对其中的
    stdout
    流执行任何操作,因为它将隐式连接到连接到管道的子进程标准输出流。然后读入父进程f从管道中读取rom,直到写入端关闭

    这种方法比使用线程(见下一点)更健壮,但它有一个潜在的缺点:如果您需要以某种方式影响嵌入式Tcl解释器,而在程序运行之前(例如,响应用户的操作),这些方式是事先不知道的,则必须在父进程和子进程之间设置某种IPC

  • 使用线程并将Tcl interp嵌入到一个单独的线程中:然后确保在另一个(我们称之为“控制”)线程中读取管道

    从表面上看,这种方法可能比分叉一个进程更简单,但随后您会遇到与线程常用的适当同步相关的所有麻烦。例如,不能直接从创建interp的线程以外的线程访问Tcl解释器。这意味着不仅要并发访问(这本身就有点明显)但由于可能出现的问题,任何访问,包括同步访问(我不确定这是否成立,但我感觉这是一个很大的蠕虫罐)

  • 所以,说了这么多,我想知道为什么您似乎系统地拒绝为您的I