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

时间:2018-08-18 19:14:08

标签: c assembly keyboard x86-16 turbo-c

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

如果在键盘上输入的字符是:

  

mmmmmmmmyyyyy nnnnnaaaaammmmmmeeeeeeee iiiiiissss HHHHaaaaiiiimmmm

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

  

我叫Haim

基本输出来自 C 中的代码,我无法触摸。我必须实现eliminate_multiple_pressuneliminate_multiple_press,而无需在两者之间碰触代码。 enter image description here

到目前为止,我编写的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来获得结果我需要。有谁知道如何完成此代码以达到预期的效果?

1 个答案:

答案 0 :(得分:2)

BIOS,DOS和 C 库(包括scanf)可以使用多层缓冲区。当计算机启动时,中断向量表被修改为将IRQ1 / INT 9h (外部键盘中断)指向BIOS例程以处理键入的字符。在最低级别,通常在circular buffer(BDA)中维护一个32字节的 6 BIOS Data Area,以跟踪字符。您可以使用Int 16h BIOS calls 1 与此低级键盘缓冲区进行交互。如果在中断时间从BIOS键盘缓冲区中删除字符,则DOS和 C scanf 5 例程将永远看不到它们。


在BIOS /中断级别消除重复字符的方法

看来,练习是通过通过中断9(IRQ1)拦截击键并丢弃重复项来消除输入到scanf 3 中的所有重复项 2 。一个新的键盘中断处理程序可以消除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 在键盘中断处理程序内,您要避免任何会 block 等待键盘输入的键盘BIOS调用。如果使用 Int 16h / AH = 0 ,请确保首先有一个可用字符, Int 16h / AH = 1 否则为 Int 16h / AH = 0 >会在等待另一个角色到达时阻塞。
  • 2 删除重复字符与禁用键盘重复率不同。
  • 3 因为在DOS例程看到重复项之前它们已经被删除(并且依赖于DOS的scanf之类的功能),所以scanf永远不会看到它们。
  • 4 可能需要进行一些修改才能与3.0x以外的Turbo-C版本兼容。
  • 5 该方法仅适用于scanf,因为它将间接进行BIOS调用以保持键盘缓冲区畅通。此代码不适用于所有通用情况,尤其是在BIOS可能缓冲击键的情况下。为了解决这个问题,键盘中断例程必须删除所有,而不是像此代码一样直接删除键盘缓冲区中的重复项。
  • 6 每个击键占用BIOS键盘缓冲区(在 BDA 中)的2个字节的空间。 32个字节中的2个丢失,因为它们用于检测键盘缓冲区是否已满或为空。这意味着BIOS可以缓冲的最大击键次数为15。