?
Solved

Keydown event in DOS?

Posted on 2003-03-17
15
Medium Priority
?
382 Views
Last Modified: 2007-12-19
I was wondering if anyone could help me out  on this.

In a program I'm making for DOS, I want to have keyboard input where the program 'instantly' responds to a specific key. However, the way it's set up, if you remember when you press and hold a key, there's the initial key response, a short delay, then the key continues to be recognized. Is there a method of keyboard input that can avoid that delay, and just have a continuous stream of the inputted character?

Any help would be appreciated, and samples of how to do so would be good as well. Keep in mind this is for a DOS program, not Win16/32.
0
Comment
Question by:Cypher19
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 5
  • 4
  • 3
  • +1
15 Comments
 

Expert Comment

by:e12voltsdac
ID: 8156636
I had this axact same question about a week ago. I finally figured it out after searching the internet forever.

For this to work you'll need to include conio.h
If you don't have it just ask me.

kbhit() will check to see if there is any information in the keyboard buffer.

getch() will input for a key.

Here is how to combine them to make something similar to what you want:

#include<conio.h>//for getch() and kbhit()
#include<iostream.h>

int main()
{

    char b=0;
    while(1)//will run forever
    {
        for(int i=0;i<999;++i)//counting loop
        {
        clrscr();//clear the screen, need conio.h
        cout<<i<<endl<<b;
        for(int k=0;k<800000;++k)//delay loop
        {}
        if(kbhit())//keyboard character check
        b=getch();//put keyboard buffer into char b
        }
    }
}
0
 

Expert Comment

by:e12voltsdac
ID: 8156637
this program will count from 0 to 999 and while its counting you can type a key, and it will display it on the second line.
0
 

Expert Comment

by:e12voltsdac
ID: 8156660
this program will count from 0 to 999 and while its counting you can type a key, and it will display it on the second line.
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 

Expert Comment

by:Maxmike
ID: 8156748
now the real trick... multiple simultanious keystrokes.
0
 

Author Comment

by:Cypher19
ID: 8157033
MaxMike: That's what one of my goals are. I can't use kbhit because I need multiple keys at once. Actually, I used kbhit + getch, but it didn't give me the result i wanted.
0
 

Expert Comment

by:Maxmike
ID: 8157406
I do know of a keyboard interrupt.  Back in the day I made a 2 player q-basic program (Yeah, I'm a bit past that now) and in qb the command was int(32)

Now, I'm not certain, but I am guessing that if you can generate an assembly intterupt 0x032 that'll somehow capture the keyboard.  I have no idea.  I don't program in assembly, nor do I know what I'm talking about.

HOWEVER, it may give you some sort of basis for figuring it out.

Alternatively it just struck me that if you could find a keyboard up command you could create some sort of control array with all the different keys you want to trap.  Now using this array you could initialize the key to '1' when kbhit for that key was found, and set it back to '0' when the key is released.  Using this method you could more accurately capture the keystrokes.  This may be the more sensical of the two ideas I've got, but I'ts late and I don't really feel like looking it up right now because I've been working on my own programming issues recently.

Let me know if this helps.
0
 

Expert Comment

by:Maxmike
ID: 8157416
hmm... you could do it by releasing the keys when the keyboard buffer is empty... But of course, then it would keep the key pressed as long as ANY button was held down.
0
 
LVL 12

Expert Comment

by:Salte
ID: 8158301
I would assume that the int(32) in basic is referring to '32' in decimal, so it should be int 0x20.

Hmm...not sure but I thought int 0x21 is the generic DOS interrupt. I thought int 0x20 was the interrupt to terminate the program or something....it's a while ago though so I may be wrong in this.

I am pretty sure that int(32) is not int 0x32 though since the first is decimal and the second is 32 in hex.

Yes, DOS does use the higher INT values for some undocumented features but I don't think reading keyboard codes are among those.

I would believe the best way to get simultaneous keyboard keys and checking for those is to read the keyboard, think it is int 0x15 or in decimal that would be int(21). This is the interrupt to directly talk with the BIOS keyboard code. Checking for simultaneous keys is simply a matter of recognizing that a key had gone down, then second key down before the first key has gone up. Then the two keys are being pressed simultaneously.

If some of those keys are shift or control it's even easier since the MS-DOS keep track of ALT SHIFT and CTRL keys and their status. If it is other keys, simply keep track of that by yourself, you get to know when a key is pressed and you get to know when it is no longer pressed.

Alf
0
 

Author Comment

by:Cypher19
ID: 8159331
MaxMike: You mean use the array in conjunction with the kbhit/getch method? That would work well, but one problem that lies with that is it can only get one key at a time.

Kind of a seperate question, but I found (with the help of some people on IRC) a windows.h function, GetAsyncKeyState(int (in hex form)), that returns a true or false based on the virtual-key code inputted if that key was pressed or not. It works great! Multiple keys at once, fairly fast, but it won't work in a DOS program/project. Is it possible to get it working in DOS, even though it's a windows function?
0
 
LVL 12

Expert Comment

by:Salte
ID: 8159689
If it is a win32 function it won't work on DOS - sorry.

However, asking the keyboard driver through int 0x15 should give you the keycode and a bit saying if the key was pressed down or if it was released. This is all the info you need. You just have to keep track of which keys are down and which are up.

If you get this sequence (one key at a time):

K1 (down), (*1) K1 (up), K2 (down), (*2) K3 (down), (*3) K2 (up), K4 (down), (*4) K3 (up), K4 (up)

Then you know that at time (*1) you have key K1 down and no other. At time (*2) you have K2 down and no other. At time (*3) you have both K2 and K3 pressed down at the same time. At time (*4) you have both K3 and K4 presesd down at the same time.

What more do you need? Check out the BIOS keyboard interface.

Alf
0
 
LVL 12

Expert Comment

by:Salte
ID: 8159727
If this doesn't work, in DOS you can always take over the keyboard event. When the user press a key the system generates an interrupt to the CPU. If you place a hook in that interrupt you can catch exactly the same information that the BIOS processes when it reads the keyboard key and places it in the FIFO buffer.

just change the interrupt vector at that place to hold your function, then when you're done processing the key and probably placing it in your own FIFO buffer and keeping track of which keys are down you can pass it on to the system keyboard driver by doing a far jump to the address of the old interrupt vector.

Note that using this method you have to read the keyboard key yourself using IN instruction.

Alf
0
 

Author Comment

by:Cypher19
ID: 8161391
Salte: And I would accomplish that how in Borland's C++? I found an interrupt property for functions (I think that's what you're talking about in the first paragraph) but I have no clue whatsoever on what to do with it.
0
 
LVL 12

Accepted Solution

by:
Salte earned 140 total points
ID: 8165310
MS-DOS has an interrupt function to set or get the interrupt vector for any interrupt.

This "system call" is used by for example TSR (Terminate and stay resident) programs to hook themselves to specific interrupts in order to extend or block certain system functions or interrupts or to sense when certain things are happening in the system.

There's a system call to get the interrupt vector, you provide a far pointer to the code that you want the system to call and the interrupt number. If you specified interrupt number 0x21 you would even hook up on the system call interrupt itself!

Such a function must be a so-called interrupt function. However, in most situations you wouldn't just make the interrupt - the interrupt is typically already served by MS-DOS so you just want to have your function called, perhaps inspect the parameters and perhaps do some other stuff and then jump to the original interrupt function. Some code like this would then do it:

void (_far * addr)();

void setup_interrupt(unsigned char x, void (_far * func)())
{
  // set up interrupt x hook.
  addr = get_interrupt(x);
  set_interrupt(x,func);
}

Here the get_interrupt() must be replaced by the interrupt to get the interrupt vector. I _think_ it is 0x21 with AX = 0x25xx where xx is the interrupt vector but it is many years since I dealt with this kind of stuff so I am most likely wrong in this assumption. However, if you browse through the interrupts defined for MS-DOS you should find it very quickly. I also believe the interrupt vector is returned in ES:BX.

Likewise set_interrupt(x,func) sets the interrupt vector to the new value. What exact interrupt this is I don't remember either, I _believe_ it is 0x21 with AX = 0x35xx but I am not at all sure about it. I also believe the func is stored in ES:BX registers before the interrupt.

Assuming those assumptions above to be correct the code would look something like this in Borland C++:

typedef void (_far * interrupt_func_t)();

interrupt_func_t get_interrupt(unsigned char x)
{
   interrupt_func_t func;
   __asm {
      mov al,x
      mov ah,0x25
      int 0x21
      // ES:BX now has the address..
      push es
      mov word ptr func,bx
      pop bx
      mov word ptr func + 2,bx
    }
    return func;
}

void set_interrupt(unsigned char x, interrupt_func_t func)
{
   __asm {
      mov al,x
      mov ah,0x35
      les bx,func
      int 0x21
   }
}

Note that you should check up the documentation about exactly which registers are used and which interrupt vector number is used for these functions as well as what value the AX register should have before the call.

In any case, if you then do setup_interrupt(x,func);

You will then 1. save the old interrupt funciton in a location (here called addr) and 2 set it to your own function func.

How this function is declared - if it is far call or near call isn't important, these things only determine how you do a ret and you don't want to do a ret from that function anyway, here is how the function should appear:

void func()
{
   blah blah blah
   // then
   terminate.
}

The blah blah blah is whatever you want to do when the interrupt occur. This function is called when the specific interrupt occur. If it is a 0x21 or some other such interrupt it is called by user code when programs do 'int 0x21'. You can then inspect the registers, for example if AH has value such and such and so on then the user want to read a file, delete a directory or whatever, this way you can trap file system access. This is what disk monitor programs under MS-DOS does, they intercept all interrupts that goes to disk.

Some interrupts are special in that they can be caused by external events. For example a keyboard event or some other such event. In modern pentium and 386 machines those interrupts actualy cause something called an 'exception'. However, MS-DOS was made in days before those 'exceptions' was made and so it knows nothing about them and all those exceptions are by default mapped to interrupts. The timer interrupt is a well known interrupt in which a timer circuit on the motherboard causes an interrupt every now and then and MS-DOS will increment a timer when that interrupt occur.

Other interrupts are external interrupts from cards and so on that are attached to the computer via the PCI or ISA or EISA bus. Your sound card and in older days your graphics card (in modern computers the graphics card usually uses its own AGP bus, but that too uses external interrupts). The hard disks and floppy disks too.

The system does not allocate an interrupt vector for each of those, instead in MS-DOS you allocated an interrupt to one, some systems required to be alone on that interrupt but others could accept that they shared the interrupt with other systems. Early sound cards was notorious for not being willing to share their interrupt with others - what was worse was that they used interrupt 5 which is the same as the 'print screen' interrupt as defined by MS-DOS. When you wanted to 'print screen', you issued an interrupt 5 and that would cause MS-DOS to print screen. However, installing a sound card might end up using that interrupt for its own purposes and the result was that print screen feature got in trouble.

Even worse was if it collided with some other hardware that also wanted to use the same interrupt and one or both of them insisted on being alone. That typically caused 'hardware conflict'.

The end result is that if you want to catch the keyboard you figure out which interrupt the keyboard uses to catch the computer's attention and then uses setup_interrupt(x,func) with x being that interrupt code and func being the function above with 'blah blah blah' being code to read the keyboard codes.

After you've done your job you need to terminate your function. what exactly that means depends on what you have done and what you plan should happen next.

If you - contrary to my assumptions - did everything that there was to do to service that interrupt and so you didn't want any other code to do anything. Then you simply return. However, you cannot do a 'return' from the function. A return would be a near or far ret instruction. A near instruction assume that you only have the return address on the stack, a far return assume you only have return address and return segment on stack. However, for interrupts you have saved EFLAGS, return segment and return address on stack and that calls for an 'return from interrupt' instruction or iret:

__asm {
   iret
}

will do a proper return in this case.

However, if you did what I assume you did - and only had a hook and did some processing of the interrupt and then want to let the regular old function continue, you don't even do that, in this case you just do a jump to the regular interrupt function. The stack will still return to whatever the cpu did when your interrupt code was called and so when that function do an iret it will return to your caller. In this case you do:

  __asm {
      jmp addr
  }

where 'addr' is the addr variable where you saved the old interrupt with get_interrupt.

When your program is about to exit and you want to reinstall the old interrupt handler you must remember to do a:

set_interrupt(x,addr);

to restore the interrupt to what it was before your program messed with it.

You can install several interrupt handlers as long as you restore each of them before you exit and as long as you save each of them in an appropriate place.

Disk monitor programs typically does that so that they can hook themselves on both 0x21 (MS-DOS system calls that access disk), 0x25/0x26 (disk read/write using MS-DOS low level) or 0x13 (BIOS disk access).

As I said, it is many years since I did this kind of things so it is possible I remember wrong here and there, in particular when talking about specific registers or values, but if you do some research you will find it easily enough. It is all well documented both by Microsoft as well as others, it is possible that you can't find the documentation at microsoft's web site any more but it sure is available elsewhere.

Hope this is of help.

Alf
0
 

Author Comment

by:Cypher19
ID: 8166742
@.@ Wow, thanks for the help salte!!
0
 
LVL 12

Expert Comment

by:Salte
ID: 8166933
One thing, if you want to jump to the function it is possible that a simple

__asm {
   jmp addr
}

isn't good. It might try to jump to the data location where the address is stored instead of considering that address to be a pointer and jump to the location indicated by it. Try:

__asm {
   jmp [addr]
}

if the first one doesn't work, you want an indirect jump.

possibly also specify that it is a far jump:

__asm {
   jmp far ptr [addr]
}

Alf
0

Featured Post

On Demand Webinar - Networking for the Cloud Era

This webinar discusses:
-Common barriers companies experience when moving to the cloud
-How SD-WAN changes the way we look at networks
-Best practices customers should employ moving forward with cloud migration
-What happens behind the scenes of SteelConnect’s one-click button

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Errors will happen. It is a fact of life for the programmer. How and when errors are detected have a great impact on quality and cost of a product. It is better to detect errors at compile time, when possible and practical. Errors that make their wa…
This article shows you how to optimize memory allocations in C++ using placement new. Applicable especially to usecases dealing with creation of large number of objects. A brief on problem: Lets take example problem for simplicity: - I have a G…
The goal of the video will be to teach the user the concept of local variables and scope. An example of a locally defined variable will be given as well as an explanation of what scope is in C++. The local variable and concept of scope will be relat…
The viewer will learn how to use the return statement in functions in C++. The video will also teach the user how to pass data to a function and have the function return data back for further processing.
Suggested Courses

743 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question