C语言中的非阻塞I/O?(Windows)

4

我正在尝试在Windows终端应用程序上实现非阻塞I/O(仅限Windows,抱歉!)。

如果我想要一个短暂的输入时间,在此期间用户可以按下按钮,但如果他不这样做,则输入停止并且程序继续怎么办?

例如:

一个计时器从1开始计数,直到用户按下某个键停止: 我应该有一个while循环,但如果我使用getch或getchar函数,它会停止程序,对吗?

我知道我可以使用kbhit();,但是对于我正在尝试制作的“程序”,我需要知道输入内容,而不仅仅是是否有输入! 是否有任何简单的函数可以允许我读取键盘缓冲区中的最后一个键?


是的,_kbhit()会告诉你有输入。调用_getch()来实际读取它,它不会阻塞。 - Hans Passant
2个回答

7
根据_kbhit()的文档

_kbhit函数检查控制台是否有最近的按键。如果函数返回非零值,则缓冲区中等待着一个按键。然后程序可以调用_getch或_getche来获取该按键。

因此,在您的循环中:

while (true) {
    // ...
    if (_kbhit()) {
        char c = _getch();
        // act on character c in whatever way you want
    }
}

所以,您仍然可以使用_getch(),但是限制其仅在_kbhit()指示有等待的内容时使用。这样它就不会阻塞。

谢谢!这不是我寻找的确切解决方案,在Linux中使用ncurses可能有更好的解决方案,但是它可以工作! - DmitryB
是的,好的,你确实特别询问了关于Windows而不是Linux的问题。 :) - Greg Hewgill
没错,我的意思是我本可以用ncurses更好地完成这个任务,但由于我特别询问Windows相关的内容,所以你提供的解决方案是目前我找到的唯一解决方案,谢谢!但还有一个问题:_kbhit()和kbhit()之间有什么区别?getch()和_getch()之间有什么区别? - DmitryB
如果我这样做: if (kbhit()) { char c = getch();} 那么这是一样的吗?getch()函数不会阻塞输入吗? - DmitryB
不,这没问题,因为getch()只有在没有输入等待时才会阻塞。通过先调用kbhit(),您确保将有等待的内容,因此getch()不会阻塞。 - Greg Hewgill
显示剩余2条评论

2

以下是如何使用正确的API在Windows中进行非阻塞调用stdin:

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <windows.h>

void ErrorExit(LPSTR);
void KeyEventProc(KEY_EVENT_RECORD ker);

// Global variables are here for example, avoid that.
DWORD fdwSaveOldMode;
HANDLE hStdin;

void printToCoordinates(int x, int y, char* text)
{
    printf("\033[%d;%dH%s", y, x, text);
}

int main()
{
    printf("\033[H\033[J");
    int i = 0;
    char* s = "*";

    DWORD fdwMode, cNumRead;
    INPUT_RECORD irInBuf[128];
    DWORD bufferSize = 0;

    hStdin = GetStdHandle(STD_INPUT_HANDLE);

    // Just a check to ensure everything is fine at this state
    if (hStdin==INVALID_HANDLE_VALUE){
        printf("Invalid handle value.\n");
        exit(EXIT_FAILURE);
    }

    // Just a check to ensure everything is fine at this state
    if (! GetConsoleMode(hStdin, &fdwSaveOldMode) )
        ErrorExit("GetConsoleMode");

    // Those constants are documented on Microsoft doc
    // ENABLE_PROCESSED_INPUT allows you to use CTRL+C
    // (so it's not catched by ReadConsoleInput here)
    fdwMode = ENABLE_WINDOW_INPUT | ENABLE_PROCESSED_INPUT;
    if (! SetConsoleMode(hStdin, fdwMode) )
        ErrorExit("SetConsoleMode");


    while (i < 60) {
        // The goal of this program is to print a line of stars
        printToCoordinates(i, 5, s);
        i++;


        GetNumberOfConsoleInputEvents(hStdin, &bufferSize);

        // ReadConsoleInput block if the buffer is empty
        if (bufferSize > 0) {
            if (! ReadConsoleInput(
                    hStdin,      // input buffer handle
                    irInBuf,     // buffer to read into
                    128,         // size of read buffer
                    &cNumRead) ) // number of records read
                ErrorExit("ReadConsoleInput");

            // This code is not rock solid, you should iterate over
            // irInBuf to get what you want, the last event may not contain what you expect
            // Once again you'll find an event constant list on Microsoft documentation
            if (irInBuf[cNumRead-1].EventType == KEY_EVENT) {
                KeyEventProc(irInBuf[cNumRead-1].Event.KeyEvent);
                Sleep(2000);
            }
        }

        Sleep(100);
    }
    // Setting the console back to normal
    SetConsoleMode(hStdin, fdwSaveOldMode);
    CloseHandle(hStdin);

    printf("\nFIN\n");

    return 0;
}

void ErrorExit (LPSTR lpszMessage)
{
    fprintf(stderr, "%s\n", lpszMessage);

    // Restore input mode on exit.

    SetConsoleMode(hStdin, fdwSaveOldMode);

    ExitProcess(0);
}

void KeyEventProc(KEY_EVENT_RECORD ker)
{
    printf("Key event: \"%c\" ", ker.uChar.AsciiChar);

    if(ker.bKeyDown)
        printf("key pressed\n");
    else printf("key released\n");
}

请注意,这个工作是在全新的终端应用程序中完成的,而不是在CMD中(由于代码中使用了termcaps),但它将编译并且您仍然可以运行它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接