【问题标题】:How to handle window events while waiting for terminal input?等待终端输入时如何处理窗口事件?
【发布时间】:2013-12-04 12:21:16
【问题描述】:

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

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

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

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

选项似乎是

  • 非阻塞 i/o
    在 unix 中相对简单。在 Windows 中看起来很痛苦
  • 轮询
  • 输入线程
    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;
}

【问题讨论】:

  • GUI 应用程序通常有一个事件循环。如果您在终端上也有 REPL,那么两个循环应该在不同的线程中运行,具体取决于它们的交互方式。为了保持用户界面的响应性,GUI 通常不应与繁重的计算在同一线程中运行。
  • 程序调用在 gui 窗口上绘图,但除此之外,窗口会自行处理。或者应该。
  • 这段代码基于in my answer here中描述的在Linux上实现getch的技术。

标签: c windows nonblocking stdio xcb


【解决方案1】:

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

更多关于Windows下的异步I/O,请看here

【讨论】:

  • 谢谢!这看起来正是我想要的。我在搜索时发现的所有内容都集中在从套接字读取。
【解决方案2】:

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

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

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

【讨论】:

  • 非常感谢您分享您的想法!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-13
  • 1970-01-01
  • 1970-01-01
  • 2012-06-19
  • 2016-04-01
  • 2021-11-14
相关资源
最近更新 更多