Подтвердить что ты не робот

Проверка наличия ключа в MS-DOS (C/С++)

Да, я имею в виду настоящую консоль MS-DOS, а не консоль Windows cmd.exe.

Есть ли способ проверить, находится ли ключ в MS-DOS, аналогично функции GetAsyncKeyState() в WinAPI?

В настоящее время я использую kbhit() и getch(), но он очень медленный, имеет задержку после первого символа, не позволяет одновременно использовать несколько ключей и т.д.

Я использую Turbo С++ 3.1. Может ли кто-нибудь помочь?

(кстати, не спрашивайте, почему я кодирую свою игру на такой древней системе)

4b9b3361

Ответ 1

Почему вы кодируете свою игру на su & hellip, просто шутите!

В MS-DOS функции "API" реализованы как сервисы прерывания. На языке ассемблера x86 вы используете команду INT и укажите номер прерывания, которое вы хотите выполнить. Большинство прерываний требуют, чтобы их "параметры" были установлены в определенных регистрах перед выполнением INT. После того, как команда INT вернет управление вашему коду, его результат будет помещен в определенные регистры и/или флаги, как определено в документации по вызову прерывания.

Я понятия не имею, как Turbo С++ реализует прерывания, поскольку это предваряет мое участие в программировании, но я знаю, что он позволяет выполнять их. Google для синтаксиса или проверьте документацию на Turbo С++.

Зная, что это прерывания, вы получите 90% пути к решению, когда будете искать. Ральф Браун собрал и опубликовал знаменитый список кодов прерываний DOS и BIOS. Они также должны быть доступны в любой книге по программированию DOS - если вы Серьезно о ретро-программировании, вы должны обязательно подумать о том, чтобы взять его в руки. Используемая копия на Amazon должна только вернуть вам несколько долларов. Большинство людей считают эти бесполезные сегодня.

Здесь - это сайт, в котором перечислены подфункции, доступные для прерывания DOS 21h. Те, которые будут иметь отношение к вашему использованию, это 01, 06, 07 и 08. Это в основном то, что стандартная библиотека C, например, getch, будет выполняться под капотом. Мне трудно представить, но я слышал сообщения о том, что программисты в тот же день быстрее набрали прерывания DOS. Причина, по которой я сомневаюсь, заключается в том, что я не могу представить, что реализаторы библиотеки времени исполнения были бы настолько глупы, чтобы обеспечить излишне медленные реализации. Но, возможно, они были.

Если прерывания DOS все еще слишком медленны для вас, ваш последний вызов будет заключаться в том, чтобы напрямую использовать прерывания BIOS. Это может иметь заметное различие в скорости, потому что вы обходите каждый слой абстракции. Но это делает вашу программу значительно менее переносимой, и именно по этой причине операционные системы, такие как DOS, обеспечивали эти вызовы функций более высокого уровня. Опять же, проверьте список Ralf Brown для прерывания, которое имеет отношение к вашему использованию. Например, INT 16 с подфункцией 01h.

Ответ 2

Нет функции, предоставляемой Turbo С++, MS-DOS или BIOS, которая соответствует функции Windows GetAsyncKeyState. BIOS только отслеживает, какие ключи модификатора (Shift, Ctrl или Alt) удерживаются, он не отслеживает ни один из других клавиш. Если вы хотите сделать это, вам нужно напрямую поговорить с контроллером клавиатуры и следить за нажатием клавиши (клавишей) и сломать (ключ), которую он получает с клавиатуры.

Для этого вам нужно будет перехватить прерывание клавиатуры (IRQ 1, INT 0x09), прочитать scancodes с контроллера клавиатуры и затем обновить собственную таблицу состояний клавиатуры.

Вот простая программа, которая демонстрирует, как это сделать:

#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 С++ 3.1 и протестирован в DOSBox и MS-DOS 6.11 под управлением VirtualBox. Он показывает текущее состояние клавиатуры строку 0 и 1, a 1, указывающую, что нажата клавиша, соответствующая коду сканирования положения. Нажмите клавишу ESC, чтобы выйти из программы.

Обратите внимание, что программа не связывает исходный обработчик клавиатуры, поэтому обычные функции клавиатуры MS-DOS и BIOS не будут работать во время перехвата клавиатуры. Также обратите внимание, что он восстанавливает исходный обработчик клавиатуры перед выходом. Это важно, потому что MS-DOS не сделает этого сам. Он также правильно обрабатывает расширенные ключи, которые отправляют два байтовых кода сканирования, что было проблемой с кодом в вопросе, с которым вы связались в своем ответе здесь.

Ответ 3

нажатие клавиш со стрелками приводит к двум прерываниям клавиатуры. (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;
}

(EDIT: скорректировано char на unsigned char, так что эта функция возвращает значение в операциях "if" с такими вещами, как scancode & 0x80)

Когда нажата клавиша, она возвращает один из scancodes, перечисленных там http://www.ctyme.com/intr/rb-0045.htm, и когда он выпущен, он возвращает тот же scancode, но ORed с 80h.

Если вы на самом деле запускаете это в игровом цикле, вы, в конце концов, переполните буфер клавиатуры BIOS, и компьютер подаст вам звуковой сигнал. Конечно, способ освободить буфер клавиатуры - это while(kbhit()) getch();, но поскольку мы находимся на 286 realmode, и у нас есть все наше оборудование для f * ck, здесь более низкоуровневое решение:

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

Если вы ищете объяснение, как и почему это работает, вы здесь:

Буфер клавиатуры BIOS начинается с 0040:001Ah и выглядит следующим образом: 2-байтовый указатель "head", 2-байтовый "хвост" и 32 байта 2-байтных scancodes. Указатель "хвост" указывает, где начать считывание из буфера клавиатуры, указатель "head" указывает, где остановиться. Поэтому, установив оба эти параметра на 0x20 (так они на самом деле указывают на 0040:0020h), мы в основном обманываем компьютер, думая, что никаких новых нажатий клавиш для извлечения нет.