4

I am trying to learn how to prevent the keyboard sending multiple chars to the screen and to scanf under DOS. I am using Turbo-C with inline assembly.

If the characters entered on the keyboard are:

mmmmmmmmyyyyy nnnnnaaaaammmmmmeeeeee iiiiiissss HHHHaaaaiiiimmmm

The characters seen on the console and processed by scanf would be:

my name is Haim

The basic output comes from the code in C which I am not allowed to touch. I must implement eliminate_multiple_press and uneliminate_multiple_presswithout touching the code in between. enter image description here

The Turbo-C code I've written so far is:

#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();
 }

Information I have been given that relate to a solution are the keyboard BIOS routines that can be found at this link:

The problems I'm having are probably related to not understanding what to do at the label notSet. The solution seems to be related to using a buffer and the register AX (especially AL), but I really have no Idea how to make scanf to get the result I need. Does anyone have any ideas how I can complete this code to achieve the desired effect?

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Haim
  • 77
  • 6
  • Are you holding down the keys for long enough for key-repeat to kick in? (Some BIOSes let you configure the key-repeat delay / rate). If it is normal key-repeat and you want to avoid it, you have two options: disable key repeat, or use a different interface that gets key-down / key-up events. There are probably BIOS calls for both; search http://www.ctyme.com/rbrown.htm. – Peter Cordes Aug 18 '18 at 19:26
  • The ancient x86 interrupt `16h` function `01h` is a BIOS call which reads the keyboard status. If a key was pressed the function returns the scan code and its ASCII character in `AH` and `AL` and the `Z` flag clear. This is more-or-less equivalent to the MS C function `kbhit()` available in `conio`. However it does not *read* the keyboard, and the status will remain until the key is read with interrupt `16h` function `00h` which is more-or-less equivalent to the MS C function `getch()`. This may explain why you get multiple values from what you think is *one* keystroke. – Weather Vane Aug 18 '18 at 19:30
  • The code is using interrupt `16h` function `04h` which is strange, because Ralf Brown says it is used variously to set the keyclick, or on a Tandy 2000, to flush the buffer. I don't see why it is here as it is undocumented in my MSDOS book. – Weather Vane Aug 18 '18 at 19:42
  • interrupt 16h function 04h - I just took it from example, I don`t think it is useful too – Haim Aug 18 '18 at 20:09
  • Weather Vane, my friend sent me a code that deals with what you said, and I think you are right about interrupt 16h function 00h. But i don`t see how it helps me to get rid of the doubles in the scanf method..... – Haim Aug 18 '18 at 20:13
  • And this should make the scanf to not get doubles??? – Haim Aug 18 '18 at 20:24
  • I still don`t get it.... If the global char not equal to AL register value (which means that I pressed the button and released it instantly), I compare it to the global char and replace it. If the char do equal to AL value, I should not need to touch the global char. how this action prevents from the scanf to not get the double chars????? somthing is missing here..... – Haim Aug 18 '18 at 20:45
  • Why are you using Turbo C in the first place? – n. m. could be an AI Aug 19 '18 at 03:27
  • @n.m. : it is an academic assignment. – Michael Petch Aug 19 '18 at 17:06
  • @MichaelPetch I refuse to recognise academic status of any institution that requires its students to use Turbo C++, and everyone should do the same. Maybe together we can stop the madness in 20-30 years from now. – n. m. could be an AI Aug 19 '18 at 17:33
  • 1
    @n.m. are you opposing TurboC itself for technical reasons (like C standards), or do you object to teaching 16-bit x86 code? I prefer Watcom-C as a choice for 16-bit development (which I still do for 80186 hardware for a client). As tool for learning the Turbo-C IDE isn't bad although the optimizer leaves something o be desired in comparison to Watcom.Teaching institutions often teach on simulators or older hardware especially in developing countries. Academia may also prefer to teach with tried and tested material. If using Turbo-C fits the course material being taught I don't see any issue. – Michael Petch Aug 19 '18 at 20:05
  • @MichaelPetch I object to teaching a non-standard C dialect and I object to teaching C tied to any particular architecture, be it 16 bit x86 or anything else. – n. m. could be an AI Aug 20 '18 at 13:30

1 Answers1

2

There are multiple layers of buffers that may be used by the BIOS, DOS, and the C library (including scanf). When your machine starts up the interrupt vector table is modified to point IRQ1/INT 9h (the external keyboard interrupt) to a BIOS routine to handle characters as they are typed. At the lowest level there is usually a 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 to interact with this low level keyboard buffer. If you remove characters from the BIOS keyboard buffer at interrupt time then DOS and the C library scanf5 routine will never see them.


Method to Eliminate Duplicate Characters at the BIOS/Interrupt Level

It appears that the exercise is to eliminate all duplicate2 characters entered into scanf3 by intercepting keystrokes via Interrupt 9 (IRQ1) and throwing duplicates away. One idea for a new keyboard interrupt handler to eliminate the duplicates before DOS (and eventually scanf) ever see them:

  • Keep track of the previous character pressed in a variable
  • Call the original (saved) Interrupt 9 so that the BIOS updates the keyboard buffer and the keyboard flags as DOS expects them to appear.
  • Query the keyboard to see if a character is available with Int 16h/AH=1h.The Zero Flag (ZF) will be set if no characters are available and clear if there is one available. This keyboard BIOS call peeks into the beginning of the keyboard buffer without actually removing the next character available.
  • 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
    • If they are the same then use Int 16h/AH=0h to remove the duplicate character from the keyboard buffer and exit

A Turbo-C 3.0x version of the code4:

#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();
}

Notes

  • 1Within the keyboard interrupt handler you want to avoid any keyboard BIOS call that will block waiting for keyboard input. If using Int 16h/AH=0 make sure there is a character available first with Int 16h/AH=1 otherwise Int 16h/AH=0 will block while waiting for another character to arrive.
  • 2Removing duplicate characters is not the same as disabling the keyboard repeat rate.
  • 3Because the duplicates are removed before DOS routines see them (and functions like scanf that rely on DOS), they will never be seen by scanf.
  • 4Some modifications may have to be made to be compatible with versions of Turbo-C other than 3.0x.
  • 5This method only works because scanf will be indirectly making BIOS calls keeping the keyboard buffer clear. This code does't work in all generic cases especially where keystrokes may be buffered by the BIOS. To get around that the keyboard interrupt routine would have to remove all the duplicates in the keyboard buffer not just at the head as this code does.
  • 6Each keystroke takes up 2 bytes of space in the BIOS keyboard buffer (in the BDA). 2 of the 32 bytes are lost because they are used to detect if the keyboard buffer is full or empty. This means the maximum number of keystrokes the BIOS can buffer is 15.
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • Its really working! Thank you very much! I But I still cannot see which line is the one that prevented the doubles? Imean, how the key variable effect on the input? Is it really necessary? Anyway thanks you! – Haim Aug 19 '18 at 08:37
  • @Haim :In the code comments `XOR AH, AH /* AH = 0 */` `INT 16h /* Read keystroke and throw it away` read the duplicate character from the keyboard buffer (**after** it has been identified)and effectively threw it away by simply removing it from the keyboard buffer. It has been removed at a point well before DOS and `scanf` ever sees it. The BIOS keyboard buffer where the characters are placed by IRQ1 (interrupt 9) are at a much lower level than `scanf` – Michael Petch Aug 19 '18 at 16:44
  • @Haim If you comment out `XOR AH, AH` `INT 16h` you should find that all the duplicates appear. – Michael Petch Aug 19 '18 at 19:44
  • 1
    Given that *kill_multiple_press* is an interrupt handler, it can only rely on the `CS` segment to be correct. Therefore I would have expected to see: `CMP AL, [CS:key]` and `MOV [CS:key], AL`. And doesn't the `CALLF` also need it? So does Turbo-C 3.0x perhaps manage the required segment registers secretly (inclusive preserving them)? – Fifoernik Aug 24 '18 at 12:57
  • 1
    @Fifoernik : That is a good question, and the answer is no. I have written other keyboard routines on SO and they specifically use CS (and I write the programs as a tiny code model where CS=DS). So the question is why are they notably absent here?This is because the code is inline assembly and unlike GCC - Turbo-C will actually generate the required code to change the segment based on the variable if the function is marked as `interrupt` – Michael Petch Aug 24 '18 at 13:07
  • @Fifoernik : I dumped the generated assembly from TCC for this code and placed it on my webserver here: http://www.capp-sysware.com/misc/stackoverflow/51911808/kbddup.asm . The interrupt handler generated the code to set _DS_ to _DGROUP_ where `Int9save` and `key` reside.DOS will of course fix up that segment at load time. – Michael Petch Aug 24 '18 at 13:55