如何在等待终端输入时处理窗口事件?

时间:2013-12-04 12:21:16

标签: c windows nonblocking stdio xcb

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

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

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

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

选项似乎是

  • 非阻塞i / o
    在unix中相对简单。看起来像是一个痛苦的窗户
  • 轮询
  • 输入线程
    并行线程?
  • 窗纱线
    并行线程?

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

如果我可以继续使用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;
}

2 个答案:

答案 0 :(得分:3)

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

有关Windows下异步I / O的更多详细信息,请参阅here

答案 1 :(得分:2)

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

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

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

我会重新使用这个原型来说明下班后的方法。 :)

编辑:花了几天时间。但这是新想法。对于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;
}