tcl/Tk 'invalid command name ""' 当通过管道控制时

tcl/Tk 'invalid command name ""' when being controlled via a pipe

我试图为 C 程序模拟一个简单的 GUI,方法是在 child 中分叉 C 程序和 exec-ing wish,然后管道传输一堆 tcl/tk 命令从 parent 进入它。创建表单后,我会让 C 程序继续读取 tcl 程序的输出并响应它。

我大部分时间都在使用它,但在这个例子中我不断收到来自 tcl 的消息:

invalid command name ""

它没有破坏任何东西,但我真的不明白是什么导致了它。

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define DIE do { perror(__FUNCTION__); exit(EXIT_FAILURE); } while (0)
#define LEN(a) (sizeof(a) / sizeof(*a))

int child(int p2c[2], int c2p[2]) {
    if (close(p2c[1])) DIE;
    if (close(c2p[0])) DIE;

    if (dup2(p2c[0], fileno(stdin)) < 0) DIE;
    if (dup2(c2p[1], fileno(stdout)) < 0) DIE;

    char * cmds[] = { "wish", NULL };
    execvp(cmds[0], cmds);
    DIE;
}

int parent(int p2c[2], int c2p[2]) {
    if (close(p2c[0])) DIE;
    if (close(c2p[1])) DIE;

    char init[] = "button .b -text {Print} -command {puts hi}\n"
                  "button .x -text {Exit} -command {exit}\n"
                  "grid .b -column 0 -row 0\n"
                  "grid .x -column 0 -row 1\n";
    if (write(p2c[1], init, LEN(init)) < LEN(init)) DIE;
    if (close(p2c[1])) DIE;

    char buf[1<<10];
    ssize_t s;

    while ((s = read(c2p[0], buf, LEN(buf))) > 0) {
        buf[s-1] = '[=13=]';

        printf("i read '%s'\n", buf);
    }

    return 0;
}

int main(int argc, char ** argv) {
    int p2c[2]; // parent to child pipe
    int c2p[2];

    if (pipe(p2c)) DIE;
    if (pipe(c2p)) DIE;

    switch (fork()) {
        case -1: DIE;              break;
        case  0: child(p2c, c2p);  break;
        default: parent(p2c, c2p); break;
    }

    return EXIT_SUCCESS;
}

知道为什么它声称空字符串是无效命令吗?

问题是你判断字符串长度的表达式是错误的。 strlen(init) 将产生一个比 LEN(init) returns 小一的值,因为后者包含 终止 NUL 字符 在计数。这意味着您将 NUL 写在管道上(它不属于的地方)并且 Tcl 将其解释为一个奇怪的(但合法的!)命令名称。没有这样的命令,当然(除非你做proc \u0000 {} {puts BOOM})但它仍然检查它在脚本自动加载机制中知道的任何东西是否可以提供该命令。 (将那个小过程添加到您发送的脚本中——记住将 \ 加倍——得到的是这条消息:

i read 'BOOM
'

QED.

如果您更改 write 以实际发送正确的字节数,一切都会如您所愿。