Windows控制台键盘输入处理:platform_console_read_key实现问询
跨平台交互式控制台输入的Windows平台实现
需求背景
我正在开发一款可同时运行在Linux和Windows系统上的交互式控制台应用程序,核心需求如下:
- 从stdin获取键盘输入,用户仅能输入特定ASCII字符且有字符数量限制
- 按下
键提交输入供程序内部状态评估 - 按下
键直接触发退出程序,无需提交输入
目前该功能仅在Linux上可用,需要补充Windows平台的实现。
现有实现架构
输入提示功能通过两个函数分层实现:
- 通用处理函数
prompt_user_input:负责输入逻辑的通用处理(输入长度限制、有效字符过滤、回显等) - 平台层函数
platform_console_read_key:负责与系统底层交互读取按键,目前仅完成Linux版本,Windows版本缺失
platform_console_read_key核心逻辑
- 立即从stdin读取最多6字节到缓冲区(用于解析非ASCII键的转义码)
- 若首字节不是转义序列标识,直接返回该ASCII字符
- 若首字节是转义序列标识:
- 剩余缓冲区为空时,返回独立键的键码
- 剩余缓冲区有内容时,解析转义序列为扩展键码(
#define定义的大于255的整数) - 无法识别的转义序列返回特殊值0
- 严重I/O错误返回特殊值
KEY_COUNT
prompt_user_input核心逻辑
- 接收参数指定允许的最大输入字符数
- 循环调用
platform_console_read_key读取按键 - 读取到
或 键时跳出循环 - 其他按键通过
filter_user_input判断有效性:- 有效且未达输入上限时,添加到输入缓冲区并回显
- 无效或达上限时跳过
问题
如何为Windows平台实现platform_console_read_key函数?具体包括:
- 如何在Windows终端立即读取6字节键盘输入?
- 如何确定输入字节的格式,将Windows键码映射为与Linux版本一致的返回值?
Linux平台参考代码
bool prompt_user_input ( const u8 char_count ) { printf ( "Type your response: " ); // Clear any previous user input. memset ( ( *state ).in , 0 , sizeof ( ( *state ).in ) ); KEY key; u8 indx = 0; for (;;) { key = platform_console_read_key (); if ( key == KEY_COUNT ) { // ERROR return false; } // Submit response? Y/N if ( key == KEY_ENTER ) { return true; } // Quit signal? Y/N if ( key == KEY_ESCAPE ) { //... return true; } // Handle backspace. if ( key == KEY_BACKSPACE ) { if ( !indx ) { continue; } indx -= 1; ( *state ).in[ indx ] = 0; } // Response too long? Y/N else if ( indx >= char_count ) { indx = char_count; continue; } // Invalid keycode? Y/N else if ( !filter_user_input ( key ) ) { continue; } // Write to input buffer. else { ( *state ).in[ indx ] = key; indx += 1; } // Render the character. printf ( "%s" , ( key == KEY_BACKSPACE ) ? "\b \b" : ( char[] ){ key , 0 } ); } } KEY platform_console_read_key ( void ) { KEY key = KEY_COUNT; // Configure terminal for non-canonical input. struct termios tty; struct termios tty_; tcgetattr ( STDIN_FILENO , &tty ); tty_ = tty; tty_.c_lflag &= ~( ICANON | ECHO ); tcsetattr ( STDIN_FILENO , TCSANOW , &tty_ ); fflush ( stdout ); // In case echo functionality desired. // Read the key from the input stream. char in[ 6 ]; // Reserve up to six bytes to handle special keys. memset ( in , 0 , sizeof ( in ) ); i32 result = read ( STDIN_FILENO , in , sizeof ( in ) ); // I/O error. if ( result < 0 ) { key = KEY_COUNT; goto platform_console_read_key_end; } // End of transmission (I/O error). if ( in[ 0 ] == 4 || in[ 1 ] == 4 || in[ 2 ] == 4 || in[ 3 ] == 4 || in[ 4 ] == 4 || in[ 5 ] == 4 ) { key = KEY_COUNT; goto platform_console_read_key_end; } // ANSI escape sequence. if ( *in == '\033' ) { // Standalone keycode. if ( !in[ 1 ] ) { key = KEY_ESCAPE; goto platform_console_read_key_end; } // Composite keycode. else { if ( in[ 1 ] == '[' ) { // ... } else { key = 0; } goto platform_console_read_key_end; } } // Standalone ASCII character. else { // Backspace key is mapped to ASCII 'delete' (for some reason). key = ( *in == KEY_DELETE ) ? KEY_BACKSPACE : *in; goto platform_console_read_key_end; } // Reset terminal to canonical input mode. platform_console_read_key_end: tcsetattr ( STDIN_FILENO , TCSANOW , &tty ); fflush ( stdout ); // In case echo functionality desired. return key; }
解决方案:Windows平台platform_console_read_key实现
使用<conio.h>头文件的_getch()函数实现无缓冲按键读取,以下是最小实现(保证与Linux版本行为一致):
KEY platform_console_read_key ( void ) { i32 getch; getch = _getch (); // Error. if ( getch < 0 ) { return KEY_COUNT; } // Standalone ASCII keycode. if ( getch != 0 && getch != 224 && getch < 0x100 ) { return newline ( getch ) ? KEY_ENTER : ( getch == '\033' ) ? KEY_ESCAPE : getch ; } // Extended keycode. getch = _getch (); switch ( getch ) { default: return 0; // Unknown keycode. } }
内容的提问来源于stack exchange,提问作者Oh Fiveight
相关产品推荐
相关产品推荐

