在MS-DOS中检查密钥是否已关闭(C/C++)

Cri*_*rPL 15 c c++ dos

是的,我的意思是真正的MS-DOS,而不是Windows的cmd.exeshell控制台.

有没有办法检查MS-DOS中的密钥是否已关闭GetAsyncKeyState(),类似于WinAPI中的功能?

目前我正在使用kbhit()getch(),但它真的很慢,在第一个字符后有延迟,不允许同时有多个键等.

我正在使用Turbo C++ 3.1.有人可以帮忙吗?

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

Cod*_*ray 13

你为什么要编写你的游戏...开个玩笑!

在MS-DOS中,"API"函数实现为中断服务器.在x86汇编语言中,您使用该INT指令并指定要执行的中断的编号.大多数中断要求在执行之前将其"参数"设置在某些寄存器中INT.在INT指令将控制权返回给您的代码之后,其结果将被放置在某些寄存器和/或标志中,如中断调用的文档所定义.

我不知道Turbo C++如何实现中断,因为那是在我参与编程之前,但我知道它允许你执行它们.谷歌周围的语法,或检查您的Turbo C++文档.

在您搜索时,了解这些是中断将使您获得90%的解决方案.Ralf Brown编译并发布了一份着名的DOS和BIOS中断代码列表.它们也应该出现在任何有关DOS编程的书中 - 如果你对复古编程很认真,那么你一定要考虑一下.在亚马逊上使用过的副本应该只能为您节省几美元.现在大多数人认为这些毫无价值.

是一个列出可用于DOS中断21h的子功能的站点.这将是有关您使用的有01,06,07,和08.这些基本上是C标准库的功能就像getch在幕后一样.我觉得很难想象,但我听说有报道说当天程序员发现直接调用DOS中断的速度更快.我质疑的原因是我无法想象运行时库实现者会如此愚蠢以至于提供不必要的慢速实现.但也许他们是.

如果DOS中断对你来说仍然太慢,你最后的办法就是直接使用BIOS中断.这可能会在速度方面产生明显的差异,因为您可能绕过每个抽象层.但它确实使你的程序显着降低了可移植性,这就是像DOS这样的操作系统首先提供这些更高级别的函数调用的原因.再次,检查Ralf Brown的列表中是否存在与您的使用相关的中断.例如,INT 16具有01h子功能.


Ros*_*dge 13

Turbo C++,MS-DOS或与Windows功能对应的BIOS没有提供任何功能GetAsyncKeyState.BIOS仅跟踪按住哪些修改键(Shift,Ctrl或Alt),它不跟踪任何其他键.如果你想这样做,你需要直接与键盘控制器通信,并监控从键盘接收的make(按下键)和中断(键释放)扫描码.

为此,您需要挂钩键盘中断(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;
}   
Run Code Online (Sandbox Code Playgroud)

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

请注意,该程序不链接原始键盘处理程序,因此键盘中断挂钩时,正常的MS-DOS和BIOS键盘功能将不起作用.另请注意,它会在退出之前恢复原始键盘处理程序.这很关键,因为MS-DOS本身不会这样做.它还可以正确处理发送两个字节扫描代码的扩展密钥,这就是您在答案中链接到的问题中的代码问题.


Cri*_*rPL 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;
}
Run Code Online (Sandbox Code Playgroud)

(编辑:更正了char到unsigned char,所以将这个函数的返回值放在"if"语句中,scancode & 0x80实际上有效)

当按下某个键时,它会返回其中一个扫描码,其中包含http://www.ctyme.com/intr/rb-0045.htm,当它被释放时,它将返回相同的扫描码,但与80h进行"或"运算.

如果你真的在游戏循环中运行它,你最终会溢出BIOS键盘缓冲区,电脑会发出哔声.一种释放键盘缓冲区的方法当然是while(kbhit()) getch();因为我们处于286实模式并且我们将所有硬件都设置为f*ck,这是一个更低级别的解决方案:

void free_keyb_buf() {
    *(char*)(0x0040001A) = 0x20;
    *(char*)(0x0040001C) = 0x20;
}
Run Code Online (Sandbox Code Playgroud)

如果您正在寻找解释如何以及为什么有效,请转到:

BIOS键盘缓冲区从此处开始,0040:001Ah如下所示:2字节"头"指针,2字节"尾部"指针和32字节2字节扫描码."tail"指针指示从键盘缓冲区开始读取的位置,"head"指针指示停止的位置.因此,通过将这两者设置为0x20(因此它们实际指向0040:0020h),我们基本上欺骗计算机,使其认为没有新的击键准备好提取.

  • 请注意,如果您正在进行实时游戏,那么您很可能真的想要制作自己的int 9处理程序.这允许您完全避免键盘轮询,这对于任何非平凡的实时控件来说都是非常必要的.如果你想要一些灵感,那么有很多古老的学校游戏已经开源了.例如,Wolfenstein 3D - https://github.com/id-Software/wolf3d/blob/master/WOLFSRC/ID_IN.C它实际上都非常简单(你没有CPU或内存来做任何复杂的事:)但是,它需要大量的低级知识. (2认同)