2

我有一个跨平台(windows 和 unix+xcb)终端+graphics_window 应用程序,它大部分工作正常,直到您在输入提示符处等待太久,在重负载下图像可能会消失。:(

我有一个用于解释器(postscript 解释器)的主循环(REPL),它每次围绕它的循环调用一个事件处理函数。事件处理程序对通常是窗口的消息/事件循环执行一次迭代。但是输入是用普通的 C i/o 处理的,因此事件处理程序在阻塞时永远不会被调用fgetc()

图形窗口仅用于输出。它没有按钮,只需要响应 Raise、Map、Expose 等事件。

如何安排在调用堆栈更深处的输入读取循环期间调用事件处理程序?这需要可以使用 POSIX 和 win32 API 来实现。

选项似乎是

  • 非阻塞 i/o
    在 unix 中相对简单。看起来像窗户的痛苦
  • 轮询
  • 输入线程
    pthreads?
  • 窗口线程
    pthreads?

这些中的任何一个都可能比其他的痛苦少吗?

如果我可以留在 unix 上,那么这似乎可以解决问题:

#include <errno.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>

void idleproc () {  /* simulates calling the event handler 
                        (ie. one slice of the window loop) */
    //printf("idle\n");
    putchar('.');
}

int idlefgetc (FILE *stream) {
    int ret;

    do {
        ret = fgetc(stream);
        idleproc();
    } while(ret == EOF && 
            (errno == EAGAIN || errno == EINTR));

    return ret;
}

int setraw (FILE *stream) {
    struct termios tbuf;
    if (tcgetattr(fileno(stream), &tbuf) == -1)
        return -1;
    tbuf.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
            | INLCR | IGNCR | ICRNL | IXON);
    tbuf.c_oflag &= ~OPOST;
    tbuf.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    tbuf.c_cflag &= ~(CSIZE | PARENB);
    tbuf.c_cflag |= CS8;
    if (tcsetattr(fileno(stream), TCSANOW, &tbuf) == -1)
        return -1;
    return 0;
}

int setnonblocking (FILE *stream) {
    int flags;

    if (setraw(stream) != 0)
        return -1;
    if (!((flags = fcntl(fileno(stream), F_GETFL)) & O_NONBLOCK)) {
        flags |= O_NONBLOCK;
        fcntl(fileno(stream), F_SETFL, flags);
    }
    return 0;
}

int main (int argc, char **argv) {
    if (setnonblocking(stdin)) {
        perror(argv[0]);
        return 0;
    }
    printf("'%d'\n", idlefgetc(stdin));
    system("stty sane");
    return 0;
}
4

2 回答 2

4

在 Windows 下,您需要使用 Console API。您可以使用ReadFileEx执行异步、非阻塞的字符读取。另一种可能性是ReadConsoleInput,并连续轮询输入,而不会阻塞。使用SetConsole,您可以决定要捕获哪些事件。

有关 Windows 下异步 I/O 的更多详细信息,请参见此处

于 2013-12-10T10:48:51.610 回答
2

另一个解决方案似乎是可能的,因为这毕竟是一个编程语言解释器。

应该可以重新实现fgetc用于代替使用 ps 原语的代码:. 这个 postscript 操作符本身使用 stdio 调用,但它可以由其他文件读取函数调用,具有延续传递样式,方法是压入 exec 堆栈并返回。这自然会交错更多对事件处理程序的调用,因为它更频繁地返回主循环。-file-readint bool

我可能仍然需要使用非阻塞读取。但是如果只在一个地方调用它会更容易管理。延续传递的优点是将较大的函数分解为单独的阶段,并已成功用于通过覆盖基类中的方法来实现窗口设备本身(基类被实现为 postscript 字典)。但它对我来说还是相当新的,所以它还不是我的首选胶带。:)

我将在工作后重新制作这个原型来说明这种方法。:)

编辑:花了几天时间。但这是新的想法。对于windows,它需要以不同的方式进行非阻塞调用,但可以将调用隔离到这个地方。并且通过延续传递,文件读取功能不需要访问(或了解)事件处理程序,因此更好的封装

该程序的行为与问题中的相同,它.重复打印直到按键,然后打印按键的 ascii 代码。模拟.在等待击键时重复调用事件处理程序。我不得不模拟一些解释器的内容:一个对象类型、一些堆栈和一个eval()函数。所以这也更好地说明了 REPL。

#include <errno.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>

int set_raw_term (FILE *stream) {
    struct termios tbuf;
    if (tcgetattr(fileno(stream), &tbuf) == -1) 
        return -1; 
    tbuf.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
            | INLCR | IGNCR | ICRNL | IXON);
    tbuf.c_oflag &= ~OPOST;
    tbuf.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    tbuf.c_cflag &= ~(CSIZE | PARENB);
    tbuf.c_cflag |= CS8;
    if (tcsetattr(fileno(stream), TCSANOW, &tbuf) == -1) 
        return -1; 
    return 0;
}

int set_nonblocking (FILE *stream) {
    int flags;

    if (set_raw_term(stream) != 0)
        return -1; 
    if (!((flags = fcntl(fileno(stream), F_GETFL)) & O_NONBLOCK)) {
        flags |= O_NONBLOCK;
        fcntl(fileno(stream), F_SETFL, flags);
    }   
    return 0;
}

int event_handler() {
    putchar('.');
}

enum { null, integer, file, operator };
typedef union {
    short tag;
    struct {
        short tag;
        int val;
    } int_;
    struct {
        short tag;
        FILE *f; 
    } file_;
    struct {
        short tag;
        int (*fp)();
    } oper_;
} object; /* object union allows multiple types on the stacks */

object os[100];   /* operand stack */
object *tos = os; /* top of operand stack */
object es[100];   /* execution stack */
object *tes = es; /* top of execution stack */

int eval () {
    if (tes == es) /* execution stack is empty */
        return -1; /* return "finished" */

    event_handler();

    switch(tes[-1].tag) { /* type of object on top of execution stack */
        case integer:
        case file:
            *tos++ = *--tes;     /* push file or integer to operand stack */
            break;

        case operator:
            (--tes)->oper_.fp(); /* call operator function */
            break;
    }
    return 0; /* return "not finished" */
}

int file_read_byte () {
    int ret;
    object arg;

    arg = *--tos; /* pop argument from operand stack */
    ret = fgetc(arg.file_.f);
    if (ret == EOF && (errno == EAGAIN || errno == EINTR)) { /* if no data */
        *tos++ = arg;   /* restore argument to operand stack */
        *tes++ = (object){ .oper_.tag = operator, .oper_.fp = file_read_byte }; /* push continuation to execution stack */
        return 0;
    } else {
        *tos++ = (object){ .int_.tag = integer, .int_.val = ret }; /* push result to operand stack */
        return 0;
    }
}


int main(int argc, char **argv) {
    int ret;

    if (set_nonblocking(stdin) != 0) {
        perror(argv[0]);
        return 0;
    }

    //printf("'%d'\n", file_read_byte(stdin));
    *tos++ = (object){ .file_.tag = file, .file_.f = stdin }; /* push file argument to operand stack */
    *tes++ = (object){ .oper_.tag = operator, .oper_.fp = file_read_byte }; /* push operator object to execution stack */

    ret = 0;
    while (ret == 0) { /* call eval until execution is "finished" */
        ret = eval();
    }
    printf("'%d'\n", (--tos)->int_.val); /* pop returned value */

    system("stty sane");
    return 0;
}
于 2014-01-07T19:14:01.097 回答