当我按下键盘上的键时如何防止重复的字符

How to prevent duplicate chars when I press keys on the keyboard

我正在尝试学习如何防止键盘将多个字符发送到屏幕和 DOS 下的 scanf。我正在使用带有内联汇编的 Turbo-C。

如果键盘输入的字符是:

mmmmmmmmyyyyy nnnnnaaaaammmmmmeeeeee iiiiiissss HHHHaaaaiiiimmmm

在控制台上看到并由 scanf 处理的字符将是:

my name is Haim

基本输出来自C中的代码,我不允许触摸。我必须实现 eliminate_multiple_pressuneliminate_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();
 }

我得到的与解决方案相关的信息是可以在 this link:

找到的键盘 BIOS 例程

我遇到的问题可能与不了解在标签 notSet 上做什么有关。该解决方案似乎与使用缓冲区和寄存器 AX(尤其是 AL)有关,但我真的不知道如何制作 scanf 得到我需要的结果。有谁知道我该如何完成这段代码以达到预期的效果?

BIOS、DOS 和C 库(包括scanf)可以使用多层缓冲区。当您的机器启动时,中断向量 table 被修改为将 IRQ1/INT 9h (外部键盘中断)指向 BIOS 例程以处理字符输入。在最低级别通常有一个 32 byte6 circular buffer that is maintained in the BIOS Data Area (BDA) to keep track of the characters. You can use the Int 16h BIOS calls1 来与这个低级别键盘缓冲区进行交互。如果您在中断时从 BIOS 键盘缓冲区中删除字符,那么 DOS 和 Cscanf5 例程将永远看不到它们。


BIOS/Interrupt级消除重复字符的方法

看来练习是通过中断拦截击键来消除输入到scanf3中的所有重复2字符9 (IRQ1) 并丢弃重复项。一个新的键盘中断处理程序的想法是在 DOS(并最终 scanf)看到它们之前消除重复:

  • 跟踪变量中按下的前一个字符
  • 调用原始(保存的)中断 9,以便 BIOS 更新键盘缓冲区和键盘标志,因为 DOS 期望它们出现。
  • 查询键盘以查看 Int 16h/AH=1h 是否有可用字符。如果没有可用字符,将设置零标志 (ZF),如果有可用字符,则清除零标志。此键盘 BIOS 调用会查看键盘缓冲区的开头,而不会实际删除下一个可用字符。
  • 如果有可用字符,则将其与前一个字符进行比较。
    • 如果它们不同则用当前字符更新前一个字符并退出
    • 如果它们相同则使用Int 16h/AH=0h从键盘缓冲区中删除重复字符并退出

Turbo-C 3.0x 版本的代码4:

#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 的函数),它们永远不会被 [=11 看到=].
  • 4可能需要进行一些修改才能与 3.0x 以外的 Turbo-C 版本兼容。
  • 5此方法之所以有效,是因为 scanf 将间接进行 BIOS 调用以保持键盘缓冲区清晰。此代码不适用于所有一般情况,尤其是在 BIOS 可能缓冲击键的情况下。为了解决这个问题,键盘中断例程必须删除 所有 键盘缓冲区中的重复项,而不仅仅是像这段代码那样在头部。
  • 6每次击键占用 BIOS 键盘缓冲区(在 BDA 中)space 的 2 个字节。 32 个字节中有 2 个丢失,因为它们用于检测键盘缓冲区是满的还是空的。这意味着 BIOS 可以缓冲的最大击键次数为 15。