获取Tcl解释器的输出
Getting the output of Tcl Interpreter
我正在尝试获得Tcl解释器的输出,如回答这个问题所描述的Tcl C API:将嵌入式Tcl接口的stdout重定向到一个文件而不影响整个程序。而不是写入数据到文件,我需要得到它使用管道。我将Tcl_OpenFileChannel
更改为Tcl_MakeFileChannel
,并将管道的写端传递给它。然后我做空了Tcl_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;
}
}
我目前没有时间修补代码(可能稍后会这样做),但我认为这种方法是有缺陷的,因为我看到了两个问题:
-
如果
stdout
连接到不是交互式控制台的东西(对isatty(2)
的调用通常由运行时使用来检查),则可以(并且我认为将)使用完整的缓冲,因此,除非您在嵌入式解释器中调用puts
输出如此多的字节以填充或溢出Tcl的通道缓冲区(8KiB, ISTR),然后下游系统的缓冲区(见下一点),其中,我认为,不小于4KiB(典型HW平台上单个内存页的大小),则在读取端不会出现任何内容。您可以通过更改Tcl脚本以刷新
stdout
来进行测试,如下所示:puts one flush stdout puts two
您应该能够从管道的读端读取第一个
puts
输出的四个字节。 -
管道是通过缓冲区连接的两个fd(缓冲区的大小已定义,但与系统有关)。一旦写端(您的Tcl接口)填满了缓冲区,将达到"buffer full"条件的写调用将阻塞写进程,除非有东西从读端读取以释放缓冲区中的空间。由于读取器是同一个进程,这样的条件很有可能会死锁,因为一旦Tcl接口在试图写
stdout
时卡住了,整个进程就会卡住。
现在的问题是:这能起作用吗?
第一个问题可以通过在Tcl端关闭该通道的缓冲来部分修复。这(假定)不会影响系统为管道提供的缓冲。
第二个问题更难,我只能想到两种可能来解决它:
-
创建一个管道,然后
fork(2)
子进程,确保其标准输出流连接到管道的写端。然后将Tcl解释器嵌入到该进程中,并且不对其中的stdout
流做任何操作,因为它将隐式地连接到附加到管道的子进程标准输出流。然后从管道读取父进程,直到写端关闭。这种方法比使用线程更健壮(参见下一点),但它有一个潜在的缺点:如果您需要以某种方式影响嵌入式Tcl解释器,而这些方式在程序运行之前是不知道的(例如,响应用户的操作),您将不得不在父进程和子进程之间设置某种IPC。
-
使用线程并将Tcl接口嵌入到一个单独的线程中:然后确保从管道中的读取发生在另一个(我们称之为"控制")线程中。
这种方法可能表面上看起来比fork进程简单,但随后就会遇到与线程常见的适当同步相关的所有麻烦。例如,Tcl解释器必须不能从创建该解释器的线程以外的线程直接访问。这不仅意味着并发访问(这本身就很明显),还意味着任何访问,包括同步访问,因为可能存在TLS问题。(我不确定这是否正确,但我有一种感觉,这是一个大麻烦。)
所以,说了这么多,我想知道为什么你似乎系统地拒绝了为你的实习实现自定义"通道驱动程序"的建议,只是用它来为你的实习提供stdout
通道的实现?这将创建一个超级简单的单线程全同步实现。这种方法到底有什么问题?
还要注意,如果您决定使用管道,希望它将作为一种"匿名文件",那么这是错误的:管道假设双方并行工作。在你的代码中,你首先让Tcl接口写所有它必须写的东西,然后试着读这个。正如我所描述的那样,这是自找麻烦,但如果这样做只是为了不弄乱文件,那么您就做错了,在POSIX系统上,操作过程可能是:
- 使用
mkstemp()
创建并打开一个临时文件。 -
立即删除它,使用名称
mkstemp()
来代替你传递给它的模板。由于文件仍然有一个打开的FD(由
mkstemp()
返回),它将从文件系统中消失,但不会被解除链接,并且可能被写入和读取。 - 使此FD为interp的
stdout
。 - 间隔结束后,
seek()
将FD返回到文件的开头并从中读取。 - 完成后关闭FD —它在底层文件系统上占用的空间将被回收。
- 递归函数计算序列中的平方和(并输出过程)
- 如何使用 < 和 > 命令获取 c++ 中的输入和输出?
- 请解释"函数1(p1,p2,p3);"的输出
- C++:将控制台输出存储在宏中更好吗
- 创建一个函数以在输入为负数或零时输出字符串.第一次执行用户定义的函数
- 如何在OMNET++中指定与命令行参数组合的输出文件名
- 为什么我的代码在输出中增加了93天
- 如何从void函数输出字符串
- 输入到文件并输出到另一个文件,并将流文件传递给函数
- AES加密到解密未正确输出
- 如何将c++程序的一些输出传递给shell,以便在shell中使用
- 使用C++程序合并排序没有得到正确的输出
- 为什么我不能在不创建字符串变量的情况下使用函数的字符串输出
- C++格式化输出问题
- 将值从二维数组输出到文本文件
- 集合上的输出迭代器:assign和increment迭代器
- 在while循环中输入带有std::cin的字符串后,控制台会输出大量胡言乱语
- 从嵌入式解释器捕获 python 窗口输出C++
- Brainfuck解释器奇怪的输出
- 获取Tcl解释器的输出