在MS-DOS (C/C++)中检查按键是否按下

15

是的,我指的是真正的MS-DOS,而不是Windows的cmd.exe shell控制台。

在MS-DOS中,是否有一种类似于WinAPI中的GetAsyncKeyState()函数的方法来检测按键是否按下?

目前我正在使用kbhit()getch(),但是它们非常慢,第一个字符后会有延迟,不允许同时按下多个键等问题。

我正在使用Turbo C++ 3.1。有人能帮忙吗?

(顺便说一句,不要问我为什么在这样一个古老的系统上编写游戏)


3
@ZachP:“不要问我为什么在这样一个古老的系统上编写我的游戏”:复古游戏永存。 - Jean-François Fabre
请参阅http://webpages.charter.net/danrollins/techhelp/0106.HTM...具体来说,是“*具有触发弹出窗口的热键的TSR通常会拦截INT 09H并使用以下序列测试某个特定键:...*” - Sinan Ünür
2
我认为您可以直接使用中断向量,而不必寻找某些C库API。 - e0k
这是一个游戏,所以控制方式基本上符合你对游戏控制的期望(例如WASD或箭头键)。“为什么使用MS-DOS?”- 只需将我的问题读到最后 ;) - CrizerPL
3
使用DOS(INT21)或BIOS(INT16)中断来检测按键是否处于“按下”状态很麻烦,最多只能得到“键位于键盘缓冲区”的信息。您可能最好提供自己的INT9处理程序并创建一个基本的“键盘驱动程序”。请参见http://stackoverflow.com/questions/19766771/need-help-hooking-new-keyboard-interrupt-in-ms-dos。 - Remus Rusanu
显示剩余4条评论
4个回答

14

Turbo C++、MS-DOS或BIOS没有提供与Windows函数GetAsyncKeyState对应的功能。 BIOS仅跟踪按下哪些修饰键(Shift、Ctrl或Alt),它不跟踪其他任何键。如果您想要做到这一点,需要直接与键盘控制器通信,并监视键盘接收到的按下和释放扫描码。

为此,您需要挂接键盘中断(IRQ 1,INT 0x09),从键盘控制器读取扫描码,然后更新自己的键盘状态表。

以下是演示如何实现此操作的简单程序:

#include <conio.h>
#include <dos.h>
#include <stdio.h>

unsigned char normal_keys[0x60];
unsigned char extended_keys[0x60];

static void interrupt 
keyb_int() {
    static unsigned char buffer;
    unsigned char rawcode;
    unsigned char make_break;
    int scancode;

    rawcode = inp(0x60); /* read scancode from keyboard controller */
    make_break = !(rawcode & 0x80); /* bit 7: 0 = make, 1 = break */
    scancode = rawcode & 0x7F;

    if (buffer == 0xE0) { /* second byte of an extended key */
        if (scancode < 0x60) {
            extended_keys[scancode] = make_break;
        }
        buffer = 0;
    } else if (buffer >= 0xE1 && buffer <= 0xE2) {
        buffer = 0; /* ingore these extended keys */
    } else if (rawcode >= 0xE0 && rawcode <= 0xE2) {
        buffer = rawcode; /* first byte of an extended key */
    } else if (scancode < 0x60) {
        normal_keys[scancode] = make_break;
    }

    outp(0x20, 0x20); /* must send EOI to finish interrupt */
}

static void interrupt (*old_keyb_int)();

void
hook_keyb_int(void) {
    old_keyb_int = getvect(0x09);
    setvect(0x09, keyb_int);
}

void
unhook_keyb_int(void) {
    if (old_keyb_int != NULL) {
        setvect(0x09, old_keyb_int);
        old_keyb_int = NULL;
    }
}

int
ctrlbrk_handler(void) {
    unhook_keyb_int();
    _setcursortype(_NORMALCURSOR);
    return 0;
}

static
putkeys(int y, unsigned char const *keys) {
    int i;
    gotoxy(1, y);
    for (i = 0; i < 0x30; i++) {
        putch(keys[i] + '0');
    }
}

void
game(void) {
    _setcursortype(_NOCURSOR);
    clrscr();
    while(!normal_keys[1]) {
        putkeys(1, normal_keys);
        putkeys(2, normal_keys + 0x30);
        putkeys(4, extended_keys);
        putkeys(5, extended_keys + 0x30);
    }
    gotoxy(1, 6);
    _setcursortype(_NORMALCURSOR);
}

int
main() {
    ctrlbrk(ctrlbrk_handler);
    hook_keyb_int();
    game();
    unhook_keyb_int();
    return 0;
}   

以上代码已使用Borland C++ 3.1编译,并在VirtualBox下运行的DOSBox和MS-DOS 6.11中进行了测试。它显示了键盘的当前状态,即一串0和1的字符串,1表示按下了与该位置扫描码对应的键。按ESC键退出程序。

请注意,程序不会链接原始键盘处理程序,因此在钩住键盘中断时,正常的MS-DOS和BIOS键盘功能将无法使用。还要注意,在退出之前,它会恢复原始的键盘处理程序。这很关键,因为MS-DOS本身不会这样做。它还正确处理发送两个字节扫描码的扩展键,这是您在此回答中链接到的问题代码的问题所在。


至少在我的Amstrad PC1512上,在输出到端口0x20以信号中断结束之前,您需要向键盘控制器发送确认。否则,键盘将不会发送更多数据。 - Bill
@Bill 这是最初的 IBM PC 和 XT 键盘接口的工作方式,但这段代码假定了一个 IBM PC/AT 键盘控制器。 - Ross Ridge

12

我开个玩笑,请问为什么你要在su上编写游戏...

在MS-DOS中,“API”函数是作为中断服务程序实现的。在x86汇编语言中,您可以使用INT指令并指定要执行的中断号。大多数中断需要在执行INT之前将其“参数”设置在某些寄存器中。当INT指令将控制返回到您的代码时,它的结果将被放置在特定的寄存器和/或标志中,如中断调用文档所定义。

我不知道Turbo C++如何实现中断,因为那是在我涉足编程之前的事情,但我知道它允许您执行它们。请在Google上查找语法或检查Turbo C++文档。

当你在搜索时,知道这些是中断会让你解决问题的90%。 Ralf Brown编译并发布了一个著名的DOS和BIOS中断代码列表。 如果你认真对待复古编程,它们也应该可以在任何一本DOS编程书籍中找到,你应该考虑去获取一本。在亚马逊上买一本二手书只需要花费几美元。大多数人现在认为这些已经没有什么用处了。

这里有一个网站列出了DOS中断21h可用的子功能。对于你使用的相关功能来说,01060708是最重要的。这些基本上就是C标准库函数getch在底层执行的操作。我觉得很难想象,但我听说过程序员们在那个年代通过直接调用DOS中断来加速处理。我质疑的原因是,我无法想象运行时库实现者会如此愚蠢地提供不必要的缓慢实现。但也许他们确实这样做了。

如果DOS中断对您来说仍然太慢,您的最后选择将是直接使用BIOS中断。这可能会在速度上产生明显的差异,因为您正在绕过所有可能的抽象层。但这确实使您的程序显着不可移植,这也是像DOS这样的操作系统提供这些更高级别的函数调用的原因。再次查看Ralf Brown的列表,找到与您使用相关的中断。例如,INT 1601h子功能

谢谢 ;) 我已经在使用int 10h来设置视频模式(VGA 320x200x16),所以我知道如何在Turbo C++中使用中断,但是不管怎样,这比预期的要困难,因为我看不到任何能帮助我同时检查多个按键状态的东西(好吧,有int 16h ah=02h,但我不想将控件限制在ctrl、shift等上)。 - CrizerPL
MSDOS 是否区分按下和释放按键?我的理解是,在按下和释放按键之后,你可以获得中断或读取一个“寄存器”;没有区别。 - Thomas Matthews
如果你真的擅长复古编程,你可以访问键盘电路,尝试找出如何检测按下哪个键以及何时释放的方法。我会搜索数据表和BIOS。 - Thomas Matthews
1
你不能使用MS-DOS或BIOS中断来实现原帖作者想要的功能。除了修饰键之外,它们都无法跟踪当前按下的哪些键。 - Ross Ridge

6

按箭头键会触发两个键盘中断?(int 09h) 这个问题中的实现很好,如果有人因某种原因想要一个现成的函数,请看这里:

unsigned char read_scancode() {
    unsigned char res;
    _asm {
        in al, 60h
        mov res, al
        in al, 61h
        or al, 128
        out 61h, al
        xor al, 128
        out 61h, al
    }
    return res;
}

(编辑:将char更正为unsigned char,以便将此函数的返回值放入类似于scancode & 0x80的“if”语句中实际起作用)

当按下一个键时,它会返回其中之一的扫描码,列在这里http://www.ctyme.com/intr/rb-0045.htm ,当它被释放时,它返回相同的扫描码,但与80h进行OR运算。

如果你在游戏循环中实际运行这个函数,你最终会溢出BIOS键盘缓冲区,计算机会发出哔哔声。当然,一种释放键盘缓冲区的方法是while(kbhit()) getch();,但由于我们处于286实模式并且有所有硬件可以操作,这里有一个更低级别的解决方案:

void free_keyb_buf() {
    *(char*)(0x0040001A) = 0x20;
    *(char*)(0x0040001C) = 0x20;
}

如果你想知道它是如何工作的以及为什么会这样,请看这里:

BIOS键盘缓冲区从0040:001Ah开始,并且长成这样:2字节的“头”指针,2字节的“尾”指针和32字节的2字节扫描码。 “尾”指针指示从键盘缓冲区开始读取的位置,“头”指针指示停止的位置。 因此,通过将它们都设置为0x20(因此它们实际上指向0040:0020h),我们基本上欺骗计算机认为没有准备好提取的新按键。


2
请注意,如果你正在开发实时游戏,你很可能真的想要制作自己的int 9处理程序。这样可以完全避免键盘轮询,这对于任何非平凡的实时控制来说都是非常必要的。如果你需要一些灵感,有很多老派游戏已经开源了;例如,《狼enstein 3D》- https://github.com/id-Software/wolf3d/blob/master/WOLFSRC/ID_IN.C。实际上,这一切都相当简单(你没有CPU或内存来做任何复杂的事情:)),但它需要大量的低级知识。 - Luaan
我同意,旧的DOS游戏通常会设置一个键盘中断处理程序,其中它们维护一个256字节的数组来存储键盘状态。然后在其他地方使用该数组以了解是否按下某个键(例如:主游戏循环)。 - tigrou
这不算是一个真正的严肃游戏,也不是我想要发布的东西,所以目前只需每秒检查30次扫描码就可以了。但是,创建一个int 09h处理程序并写入一个256字节的数组可能是保持其无漏洞的唯一方法。 - CrizerPL
所以如果你知道缓冲区在哪里,你可以遍历它来检查按下的键...我要试一试!我不知道可以访问缓冲区,很有趣。谢谢! - Edw590
顺便提一下(或许对其他人有帮助),由于我从0040:001A或0040:001C中没有得到任何信息,所以我开始寻找其他地址。从https://jeffpar.github.io/kbarchive/kb/060/Q60140/上,我得到了0000:041A(头)和0000:041C(尾),然后是16个2字节按键。还有0000:0480包含一个指向键盘缓冲区开头的指针,0000:0482包含一个指向结尾的指针。所有这些地址都是2字节指针。在DOSBox上,这些对我有效。答案中的那些似乎在这里不起作用。 - Edw590

2
所以,最近我已经浏览了所有这些东西,并且恰好有你需要的代码。(此外,我会为您提供一些很棒的书籍链接,以pdf格式获取信息。)
因此,这是如何工作的,您需要在内存中覆盖中断向量表的索引9h处。中断向量表只是一个内存地址表,指向当触发该中断时要运行的代码片段(这些称为中断处理程序例程或ISR)。键盘控制器准备使用扫描码时,将触发中断9h。
无论如何,我们首先需要通过调用KeyboardInstallDriver()函数来覆盖旧的int9h ISR。现在,当int9h被触发时,将调用KeyboardIsr()函数,并从键盘控制器获取扫描码,并根据从键盘控制器检索到的扫描码的值,在keyStates[]数组中设置一个值为1(KEY_PRESSED)或0(KEY_RELEASED)。
在相应的keyStates[]数组中的值已设置后,然后可以调用KeyboardGetKey(),给出要知道状态的键的扫描码,它将在keyStates[]数组中查找并返回状态。
这个内容有很多细节,但是在这里写太多了。所有的细节都可以在我会提供的书籍中找到链接: IBM PC 技术参考手册IBM PC XT 技术参考手册IBM PC AT 技术参考手册3D 游戏编程黑魔法
希望这些链接能保持一段时间的有效性。此外,《3D游戏编程黑魔法》这本书并不总是在每个小细节上都完全准确。有时候会有错别字,有时候会有错误信息,但IBM技术参考手册包含了所有细节(尽管有时它们有点晦涩),但它们没有示例代码。使用这本书来获得一般想法,并使用参考手册来获取详细信息。

这是我从键盘获取输入的代码: (它并没有完全针对所有可能的按键和某些其他事情进行完成,但对于大多数程序和游戏来说它工作得非常好。)

此外,还有一些处理“扩展”键的代码。扩展键的常规扫描码前缀为0xE0。甚至还有更多疯狂的细节,所以我不会涵盖它,但是这是基本可行的代码。

keyboard.h

#ifndef KEYBOARD_H_INCLUDED
#define KEYBOARD_H_INCLUDED

#include "keyboard_scan_codes.h"

unsigned char   KeyboardGetKey(unsigned int scanCode);
void            KeyboardClearKeys();
void            KeyboardInstallDriver();
void            KeyboardUninstallDriver();
void            KeyboardDumpScancodeLog();

#endif // KEYBOARD_H_INCLUDED

keyboard.c

#define MAX_SCAN_CODES 256
#define KEYBOARD_CONTROLLER_OUTPUT_BUFFER 0x60
#define KEYBOARD_CONTROLLER_STATUS_REGISTER 0x64
#define KEY_PRESSED 1
#define KEY_RELEASED 0
#define PIC_OPERATION_COMMAND_PORT 0x20
#define KEYBOARD_INTERRUPT_VECTOR 0x09

// PPI stands for Programmable Peripheral Interface (which is the Intel 8255A chip)
// The PPI ports are only for IBM PC and XT, however port A is mapped to the same
// I/O address as the Keyboard Controller's (Intel 8042 chip) output buffer for compatibility.
#define PPI_PORT_A 0x60
#define PPI_PORT_B 0x61
#define PPI_PORT_C 0x62
#define PPI_COMMAND_REGISTER 0x63

#include <dos.h>
#include <string.h>
#include <stdio.h>
#include <conio.h>

#include "keyboard.h"

void interrupt (*oldKeyboardIsr)() = (void *)0;

unsigned char keyStates[MAX_SCAN_CODES];

unsigned char keyCodeLog[256] = {0};
unsigned char keyCodeLogPosition = 0;
static unsigned char isPreviousCodeExtended = 0;

unsigned char KeyboardGetKey(unsigned int scanCode)
{
    // Check for the extended code
    if(scanCode >> 8 == 0xE0)
    {
        // Get rid of the extended code
        scanCode &= 0xFF;
        return keyStates[scanCode + 0x7F];
    }
    else
    {
        return keyStates[scanCode];
    }
}

void KeyboardClearKeys()
{
    memset(&keyStates[0], 0, MAX_SCAN_CODES);
}

void interrupt far KeyboardIsr()
{
    static unsigned char scanCode;
    unsigned char ppiPortB;

    _asm {
        cli // disable interrupts
    };

    /* The keyboard controller, by default, will send scan codes
    // in Scan Code Set 1 (reference the IBM Technical References
    // for a complete list of scan codes).
    //
    // Scan codes in this set come as make/break codes. The make
    // code is the normal scan code of the key, and the break code
    // is the make code bitwise "OR"ed with 0x80 (the high bit is set).
    //
    // On keyboards after the original IBM Model F 83-key, an 0xE0
    // is prepended to some keys that didn't exist on the original keyboard.
    //
    // Some keys have their scan codes affected by the state of
    // the shift, and num-lock keys. These certain
    // keys have, potentially, quite long scan codes with multiple
    // possible 0xE0 bytes along with other codes to indicate the
    // state of the shift, and num-lock keys.
    //
    // There are two other Scan Code Sets, Set 2 and Set 3. Set 2
    // was introduced with the IBM PC AT, and Set 3 with the IBM
    // PS/2. Set 3 is by far the easiest and most simple set to work
    // with, but not all keyboards support it.
    //
    // Note:
    // The "keyboard controller" chip is different depending on
    // which machine is being used. The original IBM PC uses the
    // Intel 8255A-5, while the IBM PC AT uses the Intel 8042 (UPI-42AH).
    // On the 8255A-5, port 0x61 can be read and written to for various
    // things, one of which will clear the keyboard and disable it or
    // re enable it. There is no such function on the AT and newer, but
    // it is not needed anyways. The 8042 uses ports 0x60 and 0x64. Both
    // the 8255A-5 and the 8042 give the scan codes from the keyboard
    // through port 0x60.

    // On the IBM PC and XT and compatibles, you MUST clear the keyboard
    // after reading the scancode by reading the value at port 0x61,
    // flipping the 7th bit to a 1, and writing that value back to port 0x61.
    // After that is done, flip the 7th bit back to 0 to re-enable the keyboard.
    //
    // On IBM PC ATs and newer, writing and reading port 0x61 does nothing (as far
    // as I know), and using it to clear the keyboard isn't necessary.*/

    scanCode = 0;
    ppiPortB = 0;

    ppiPortB = inp(PPI_PORT_B); // get the current settings in PPI port B
    scanCode = inp(KEYBOARD_CONTROLLER_OUTPUT_BUFFER); // get the scancode waiting in the output buffer
    outp(PPI_PORT_B, ppiPortB | 0x80); // set the 7th bit of PPI port B (clear keyboard)
    outp(PPI_PORT_B, ppiPortB); // clear the 7th bit of the PPI (enable keyboard)

    // Log scancode
    keyCodeLog[keyCodeLogPosition] = scanCode;
    if(keyCodeLogPosition < 255)
    {
        ++keyCodeLogPosition;
    }


    // Check to see what the code was.
    // Note that we have to process the scan code one byte at a time.
    // This is because we can't get another scan code until the current
    // interrupt is finished.
    switch(scanCode)
    {
    case 0xE0:
        // Extended scancode
        isPreviousCodeExtended = 1;
        break;
    default:
        // Regular scancode
        // Check the high bit, if set, then it's a break code.
        if(isPreviousCodeExtended)
        {
            isPreviousCodeExtended = 0;
            if(scanCode & 0x80)
            {
                scanCode &= 0x7F;
                keyStates[scanCode + 0x7F] = KEY_RELEASED;
            }
            else
            {
                keyStates[scanCode + 0x7F] = KEY_PRESSED;
            }
        }
        else if(scanCode & 0x80)
        {
            scanCode &= 0x7F;
            keyStates[scanCode] = KEY_RELEASED;
        }
        else
        {
            keyStates[scanCode] = KEY_PRESSED;
        }
        break;
    }

    // Send a "Non Specific End of Interrupt" command to the PIC.
    // See Intel 8259A datasheet for details.
    outp(PIC_OPERATION_COMMAND_PORT, 0x20);

    _asm
    {
        sti // enable interrupts
    };
}

void KeyboardInstallDriver()
{
    // Make sure the new ISR isn't already in use.
    if(oldKeyboardIsr == (void *)0)
    {
        oldKeyboardIsr = _dos_getvect(KEYBOARD_INTERRUPT_VECTOR);
        _dos_setvect(KEYBOARD_INTERRUPT_VECTOR, KeyboardIsr);
    }
}

void KeyboardUninstallDriver()
{
    // Make sure the new ISR is in use.
    if(oldKeyboardIsr != (void *)0)
    {
        _dos_setvect(KEYBOARD_INTERRUPT_VECTOR, oldKeyboardIsr);
        oldKeyboardIsr = (void *)0;
    }
}

void KeyboardDumpScancodeLog()
{
    FILE *keyLogFile = fopen("keylog.hex", "w+b");
    if(!keyLogFile)
    {
        printf("ERROR: Couldn't open file for key logging!\n");
    }
    else
    {
        int i;
        for(i = 0; i < 256; ++i)
        {
            fputc(keyCodeLog[i], keyLogFile);
        }


        fclose(keyLogFile);
    }
}

键盘扫描码.h(仅将所有扫描码定义为QWERTY按钮布局)
#ifndef KEYBOARD_SCAN_CODES_H_INCLUDED
#define KEYBOARD_SCAN_CODES_H_INCLUDED


// Original 83 Keys from the IBM 83-key Model F keyboard
#define SCAN_NONE              0x00
#define SCAN_ESC               0x01
#define SCAN_1                 0x02
#define SCAN_2                 0x03
#define SCAN_3                 0x04
#define SCAN_4                 0x05
#define SCAN_5                 0x06
#define SCAN_6                 0x07
#define SCAN_7                 0x08
#define SCAN_8                 0x09
#define SCAN_9                 0x0A
#define SCAN_0                 0x0B
#define SCAN_MINUS             0x0C
#define SCAN_EQUALS            0x0D
#define SCAN_BACKSPACE         0x0E
#define SCAN_TAB               0x0F
#define SCAN_Q                 0x10
#define SCAN_W                 0x11
#define SCAN_E                 0x12
#define SCAN_R                 0x13
#define SCAN_T                 0x14
#define SCAN_Y                 0x15
#define SCAN_U                 0x16
#define SCAN_I                 0x17
#define SCAN_O                 0x18
#define SCAN_P                 0x19
#define SCAN_LEFT_BRACE        0x1A
#define SCAN_RIGHT_BRACE       0x1B
#define SCAN_ENTER             0x1C
#define SCAN_LEFT_CONTROL      0x1D
#define SCAN_A                 0x1E
#define SCAN_S                 0x1F
#define SCAN_D                 0x20
#define SCAN_F                 0x21
#define SCAN_G                 0x22
#define SCAN_H                 0x23
#define SCAN_J                 0x24
#define SCAN_K                 0x25
#define SCAN_L                 0x26
#define SCAN_SEMICOLON         0x27
#define SCAN_APOSTROPHE        0x28
#define SCAN_ACCENT            0x29
#define SCAN_TILDE             0x29 // Duplicate of SCAN_ACCENT with popular Tilde name.
#define SCAN_LEFT_SHIFT        0x2A
#define SCAN_BACK_SLASH        0x2B
#define SCAN_Z                 0x2C
#define SCAN_X                 0x2D
#define SCAN_C                 0x2E
#define SCAN_V                 0x2F
#define SCAN_B                 0x30
#define SCAN_N                 0x31
#define SCAN_M                 0x32
#define SCAN_COMMA             0x33
#define SCAN_PERIOD            0x34
#define SCAN_FORWARD_SLASH     0x35
#define SCAN_RIGHT_SHIFT       0x36
#define SCAN_KP_STAR           0x37
#define SCAN_KP_MULTIPLY       0x37 // Duplicate of SCAN_KP_STAR
#define SCAN_LEFT_ALT          0x38
#define SCAN_SPACE             0x39
#define SCAN_CAPS_LOCK         0x3A
#define SCAN_F1                0x3B
#define SCAN_F2                0x3C
#define SCAN_F3                0x3D
#define SCAN_F4                0x3E
#define SCAN_F5                0x3F
#define SCAN_F6                0x40
#define SCAN_F7                0x41
#define SCAN_F8                0x42
#define SCAN_F9                0x43
#define SCAN_F10               0x44
#define SCAN_NUM_LOCK          0x45
#define SCAN_SCROLL_LOCK       0x46
#define SCAN_KP_7              0x47
#define SCAN_KP_8              0x48
#define SCAN_KP_9              0x49
#define SCAN_KP_MINUS          0x4A
#define SCAN_KP_4              0x4B
#define SCAN_KP_5              0x4C
#define SCAN_KP_6              0x4D
#define SCAN_KP_PLUS           0x4E
#define SCAN_KP_1              0x4F
#define SCAN_KP_2              0x50
#define SCAN_KP_3              0x51
#define SCAN_KP_0              0x52
#define SCAN_KP_PERIOD         0x53

// Extended keys for the IBM 101-key Model M keyboard.
#define SCAN_RIGHT_ALT         0xE038
#define SCAN_RIGHT_CONTROL     0xE01D
#define SCAN_LEFT_ARROW        0xE04B
#define SCAN_RIGHT_ARROW       0xE04D
#define SCAN_UP_ARROW          0xE048
#define SCAN_DOWN_ARROW        0xE050
#define SCAN_NUMPAD_ENTER      0xE01C
#define SCAN_INSERT            0xE052
#define SCAN_DELETE            0xE053
#define SCAN_HOME              0xE047
#define SCAN_END               0xE04F
#define SCAN_PAGE_UP           0xE049
#define SCAN_PAGE_DOWN         0xE051
#define SCAN_KP_FORWARD_SLASH  0xE035
#define SCAN_PRINT_SCREEN      0xE02AE037

#endif // KEYBOARD_SCAN_CODES_H_INCLUDED

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