Как предотвратить дублирование символов при нажатии клавиш на клавиатуре

Я пытаюсь узнать, как предотвратить отправку с клавиатуры нескольких символов на экран и в scanf под DOS. Я использую Turbo-C со встроенной сборкой.

Если символы, введенные с клавиатуры:

ммммммммгггггг нннннаааааммммммеееее iiiiiissss HHHHaaaaiiiimmmm

Символы, видимые на консоли и обработанные scanf, будут следующими:

меня зовут Хаим

Основной вывод исходит из кода на C, который мне не разрешено трогать. Я должен реализовать eliminate_multiple_press и uneliminate_multiple_press, не касаясь промежуточного кода. введите здесь описание изображения

Код Turbo-C, который я написал до сих пор:

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

volatile char key;
volatile int i=0;
void interrupt (*Int9save) (void);

void interrupt kill_multiple_press()
{
 asm{
     MOV AL, 0
     MOV AH,1
     INT 16h
     PUSHF
     CALL DWORD PTR Int9save
     MOV AX,0
 }

 asm{
  JZ notSet
  MOV key, AL
  MOV AH, 04H
  INT 16H

 }
 notSet:
 //I am not sure what to do from here...............
  I also know that it should be related to the zero flag, but what I          
  wrote so far didn`t effect on multiple characters.
}

void eliminate_multiple_press()
{
 Int9save=getvect(9);
 setvect(9,kill_multiple_press);
}

void uneliminate_multiple_press()
{
  setvect(9,Int9save);
}

void main()
{
  char str[10000]="";
  clrscr();
  eliminate_multiple_press();
  printf("Enter your string: ");
  scanf("%s",&str);
  printf("\n%s",str);
  uneliminate_multiple_press();
 }

Полученная мной информация, относящаяся к решению, — это подпрограммы BIOS клавиатуры, которые можно найти по этой ссылке:

Проблемы, которые у меня возникают, вероятно, связаны с непониманием того, что делать на метке notSet. Решение похоже связано с использованием буфера и регистра AX (особенно AL), но я действительно понятия не имею, как сделать scanf, чтобы получить нужный мне результат. Есть ли у кого-нибудь идеи, как я могу завершить этот код для достижения желаемого эффекта?


person Haim    schedule 18.08.2018    source источник
comment
Вы удерживаете клавиши достаточно долго, чтобы сработало повторение клавиш? (Некоторые BIOS позволяют настраивать задержку/частоту повторения клавиш). Если это обычное повторение клавиш и вы хотите его избежать, у вас есть два варианта: отключить повторение клавиш или использовать другой интерфейс, который получает события нажатия/нажатия клавиши. Вероятно, есть вызовы BIOS для обоих; найдите ctyme.com/rbrown.htm.   -  person Peter Cordes    schedule 18.08.2018
comment
Древняя функция прерывания x86 16h функция 01h — это вызов BIOS, который считывает состояние клавиатуры. Если клавиша была нажата, функция возвращает код сканирования и его символ ASCII в AH и AL, а флаг Z сбрасывается. Это более или менее эквивалентно функции MS C kbhit(), доступной в conio. Однако он не читает клавиатуру, и статус сохраняется до тех пор, пока клавиша не будет прочитана прерыванием 16h функцией 00h, которая более или менее эквивалентна функции MS C getch(). Это может объяснить, почему вы получаете несколько значений от того, что вы считаете одним нажатием клавиши.   -  person Weather Vane    schedule 18.08.2018
comment
В коде используется функция прерывания 16h 04h, что странно, поскольку Ральф Браун говорит, что она используется по-разному для установки нажатия клавиши или на Tandy 2000 для очистки буфера. Я не понимаю, почему это здесь, поскольку это не задокументировано в моей книге MSDOS.   -  person Weather Vane    schedule 18.08.2018
comment
прерывание 16h функция 04h - я просто взял это из примера, я не думаю, что это тоже полезно   -  person Haim    schedule 18.08.2018
comment
Флюгер, мой друг прислал мне код, который относится к тому, что вы сказали, и я думаю, что вы правы насчет функции прерывания 16h 00h. Но я не понимаю, как это поможет мне избавиться от двойников в методе scanf.....   -  person Haim    schedule 18.08.2018
comment
И это должно сделать так, чтобы сканф не получал дублей???   -  person Haim    schedule 18.08.2018
comment
Я так и не понял.... Если глобальный char не равен значению регистра AL (что означает, что я нажал кнопку и тут же отпустил ее), я сравниваю его с глобальным char и заменяю. Если символ равен значению AL, мне не нужно трогать глобальный символ. как это действие мешает сканфу не получать двойные символы????? чего-то здесь не хватает.....   -  person Haim    schedule 18.08.2018
comment
Почему вы используете Turbo C в первую очередь?   -  person n. 1.8e9-where's-my-share m.    schedule 19.08.2018
comment
@н.м. : это академическое задание.   -  person Michael Petch    schedule 19.08.2018
comment
@MichaelPetch Я отказываюсь признавать академический статус любого учебного заведения, которое требует от своих студентов использования Turbo C++, и все должны делать то же самое. Может быть, вместе мы сможем остановить это безумие лет через 20-30.   -  person n. 1.8e9-where's-my-share m.    schedule 19.08.2018
comment
@н.м. вы выступаете против самого TurboC по техническим причинам (например, стандарты C) или вы возражаете против обучения 16-битному коду x86? Я предпочитаю Watcom-C как выбор для 16-битной разработки (что я все еще делаю для оборудования 80186 для клиента). В качестве инструмента для обучения Turbo-C IDE неплох, хотя оптимизатор оставляет желать лучшего по сравнению с Watcom. Учебные заведения часто проводят обучение на симуляторах или старом оборудовании, особенно в развивающихся странах. Академические круги также могут предпочесть преподавать на проверенном материале. Если использование Turbo-C соответствует изучаемому материалу курса, я не вижу никаких проблем.   -  person Michael Petch    schedule 19.08.2018
comment
@MichaelPetch Я возражаю против обучения нестандартному диалекту C, и я возражаю против обучения C, привязанного к какой-либо конкретной архитектуре, будь то 16-битный x86 или что-то еще.   -  person n. 1.8e9-where's-my-share m.    schedule 20.08.2018


Ответы (1)


Существует несколько уровней буферов, которые могут использоваться BIOS, DOS и библиотекой C (включая scanf). Когда ваша машина запускается, таблица векторов прерываний модифицируется, чтобы указать IRQ1/INT 9h (внешнее прерывание от клавиатуры) на подпрограмму BIOS для обработки символов по мере их ввода. На самом низком уровне обычно находится 32-байтовый 6 циклический буфер. который хранится в Области данных BIOS (BDA) для отслеживания символов. Вы можете использовать вызовы Int 16h BIOS1 для взаимодействия с этим низкоуровневым буфером клавиатуры. Если вы удалите символы из буфера клавиатуры BIOS во время прерывания, то DOS и подпрограмма библиотеки C scanf5 никогда их не увидят.


Метод устранения повторяющихся символов на уровне BIOS/прерываний

Похоже, что упражнение состоит в том, чтобы исключить все повторяющиеся символы2, введенные в scanf3, путем перехвата нажатий клавиш с помощью прерывания 9 (IRQ1) и отбрасывания дубликатов. Одна идея для нового обработчика прерываний клавиатуры для устранения дубликатов до того, как DOS (и, в конечном итоге, scanf) их увидит:

  • Следите за предыдущим символом, нажатым в переменной
  • Вызовите исходное (сохраненное) прерывание 9, чтобы BIOS обновил буфер клавиатуры и флаги клавиатуры в соответствии с ожиданиями DOS.
  • Запросите клавиатуру, чтобы узнать, доступен ли символ с Int 16h/AH=1h.Флажок нуля (ZF) будет установлен, если нет доступных символов, и сброшен, если он есть. Этот вызов BIOS клавиатуры заглядывает в начало буфера клавиатуры, фактически не удаляя следующий доступный символ.
  • If a character is available then compare it with the previous character.
    • If they are different then update the previous character with the current character and exit
    • Если они совпадают, используйте Int 16h/AH=0h, чтобы удалить дублировать символ из буфера клавиатуры и выйти

Версия кода Turbo-C 3.0x4:

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

volatile char key = 0;
void interrupt (*Int9save)(void);

void interrupt kill_multiple_press(void)
{
    asm {
     PUSHF
     CALL DWORD PTR Int9save       /* Fake an interrupt call to original handler */

     MOV AH, 1                     /* Peek at next key in buffer without removing it */
     INT 16h                     
     JZ noKey                      /* If no keystroke then we are finished */
                                   /*     If ZF=1 then no key */

     CMP AL, [key]                 /* Compare key to previous key */
     JNE updChar                   /*     If characters are not same, update */
                                   /*     last character and finish */

     /* Last character and current character are same (duplicate)
      * Read keystroke from keyboard buffer and throw it away (ignore it)
      * When it is thrown away DOS and eventually `scanf` will never see it */
     XOR AH, AH                    /* AH = 0, Read keystroke BIOS Call */

     INT 16h                       /* Read keystroke that has been identified as a */
                                   /*     duplicate in keyboard buffer and throw away */
     JMP noKey                     /* We are finished */
    }
updChar:
    asm {
     MOV [key], AL                 /* Update last character pressed */
    }
noKey:                             /* We are finished */
}

void eliminate_multiple_press()
{
    Int9save = getvect(9);
    setvect(9, kill_multiple_press);
}

void uneliminate_multiple_press()
{
    setvect(9, Int9save);
}

void main()
{
    char str[1000];
    clrscr();
    eliminate_multiple_press();
    printf("Enter your string: ");
    /* Get a string terminated by a newline. Max 999 chars + newline */
    scanf("%999[^\n]s", &str);
    printf("\n%s", str);
    uneliminate_multiple_press();
}

Примечания

  • 1В обработчике прерывания клавиатуры вы хотите избежать любых вызовов клавиатуры BIOS, которые блокируют ожидание ввода с клавиатуры. При использовании Int 16h/AH=0 убедитесь, что первым доступен символ с Int 16h/AH=1, иначе Int 16h/AH=0 > будет блокироваться в ожидании прибытия другого персонажа.
  • 2Удаление повторяющихся символов — это не то же самое, что отключение частоты повторения клавиатуры.
  • 3Поскольку дубликаты удаляются до того, как их увидят подпрограммы DOS (и такие функции, как scanf, зависящие от DOS), scanf никогда их не увидит.
  • 4Возможно, потребуется внести некоторые изменения для совместимости с версиями Turbo-C, отличными от 3.0x.
  • 5Этот метод работает только потому, что scanf будет косвенно выполнять вызовы BIOS, поддерживая очистку буфера клавиатуры. Этот код не работает во всех общих случаях, особенно там, где BIOS может буферизовать нажатия клавиш. Чтобы обойти это, процедура прерывания клавиатуры должна будет удалить все дубликаты в буфере клавиатуры, а не только в начале, как это делает этот код.
  • 6Каждое нажатие клавиши занимает 2 байта в буфере клавиатуры BIOS (в BDA). 2 из 32 байтов теряются, потому что они используются для определения того, заполнен или пуст буфер клавиатуры. Это означает, что максимальное количество нажатий клавиш, которое может буферизовать BIOS, равно 15.
person Michael Petch    schedule 18.08.2018
comment
Это действительно работает! Большое спасибо! I Но я все еще не могу понять, какая линия предотвратила дублирование? Имеешь в виду, как ключевая переменная влияет на ввод? Это действительно необходимо? В любом случае спасибо вам! - person Haim; 19.08.2018
comment
@Haim: в комментариях к коду XOR AH, AH /* AH = 0 */ INT 16h /* Read keystroke and throw it away прочитайте повторяющийся символ из буфера клавиатуры (после того, как он был идентифицирован) и эффективно выбросьте его, просто удалив его из буфера клавиатуры. Он был удален задолго до того, как DOS и scanf увидели его. Буфер клавиатуры BIOS, в который помещаются символы с помощью IRQ1 (прерывание 9), находится на гораздо более низком уровне, чем scanf - person Michael Petch; 19.08.2018
comment
@Haim Если вы закомментируете XOR AH, AH INT 16h, вы обнаружите, что появляются все дубликаты. - person Michael Petch; 19.08.2018
comment
Учитывая, что kill_multiple_press является обработчиком прерывания, он может полагаться только на правильность сегмента CS. Поэтому я ожидал увидеть: CMP AL, [CS:key] и MOV [CS:key], AL. А CALLF тоже не надо? Так может быть, Turbo-C 3.0x тайно управляет требуемыми регистрами сегментов (включая их сохранение)? - person Fifoernik; 24.08.2018
comment
@Fifoernik: Это хороший вопрос, и ответ - нет. Я написал другие клавиатурные подпрограммы на SO, и они специально используют CS (и я пишу программы в виде крошечной модели кода, где CS = DS). Итак, вопрос в том, почему они здесь заметно отсутствуют? Это потому, что код представляет собой встроенный ассемблер и, в отличие от GCC, Turbo-C фактически сгенерирует необходимый код для изменения сегмента на основе переменной, если функция помечена как interrupt - person Michael Petch; 24.08.2018
comment
@Fifoernik: я выгрузил сгенерированную сборку из TCC для этого кода и поместил ее на свой веб-сервер здесь: capp-sysware.com/misc/stackoverflow/51911808/kbddup.asm . Обработчик прерывания сгенерировал код для установки DS в DGROUP, где находятся Int9save и key. DOS, конечно же, исправит этот сегмент во время загрузки. - person Michael Petch; 24.08.2018