You are on page 1of 4

The QBNews Page 22

Volume 1, Number 3 May 22, 1990


In Search of the Elusive UEVENT by Jim Mack
QB allows you to trap a number of different "events", such as the
TIMER tick, the arrival of a byte in a COM buffer, the press of a
specific KEY, and so on. It does so by adding extra code after
each statement (/V) or each line (/W) which checks the state of flags
associated with enabled events. Special handlers deal with the
actual interrupt and set these internal flags as appropriate.
This is a three-stage process: first, an interrupt (an event)
occurs and is handled quickly by the QB runtime, which sets the flag
and variables associated with the event. This happens behind your
program's back, as it were. Second, when the current line or
statement completes, QB checks the flags to see if any active events
(those for which you have executed "ON xxx GOSUB" and "xxx ON"
commands) have occurred. Third, on discovering such a condition QB
executes the GOSUB code you wrote to deal with it.
The only area where UEVENT differs from events like KEY is in the
first part of the first step. In defining a UEVENT, _you_ take
responsibility for dealing with the interrupt, and for notifying the
QB runtime that such an event has occurred. From that point on, the
action is exactly the same.
The difference between invoking a GOSUB via SetUEvent (or any
trap) and calling it directly is that when you invoke it, it's
executed only when QB gets around to it, and only if UEVENT ON is
currently in effect. A side effect of this is that you may "lose"
events if more than one occurs between occasions when QB checks its
internal flags.
This whole business of interrupts can be broken down in several
ways: shared vs. exclusive vs. chained, or software vs. hardware, and
so on. The code packages included here give examples of two common
combinations.
You can trigger a UEvent in QB with no interrupt at all, by just
saying "CALL SetUEvent". In MASM, declaring SetUEvent as an EXTRN far
procedure lets you do the same thing: CALL SetUEvent. In C, you'd
declare "setuevent" as a void far external function and then reference
"setuevent()" to cause your QB handler to be invoked. Simple... and
practically useless by itself. You need to combine this with a
software or hardware interrupt.
>> "Software interrupts" are really misnamed: they have more in
>> common with a subroutine call than with a hardware interrupt.
>> Since they occur under direct program control, there's nothing
>> unexpected or asynchronous about them. They do however use
>> the same table of vectors that the hardware interrupts use.
A small step up is the exclusive "true" software interrupt. This
involves taking over an unused interrupt vector, writing a tiny MASM
routine which intercepts INTs directed at this vector and performs a
CALL SetUEvent. There's no reason to take this extra step unless
you're working with a canned other-language program which must use a
The QBNews Page 23
Volume 1, Number 3 May 22, 1990
pre-defined INT to access your code. If you're using DOS 3.x, this
can be done in exactly the same manner as the "chained" software
interrupt described below, since what you're chaining onto is a
pre-defined Dismiss This Interrupt routine.
>> A "vector" in this context is a memory location reserved by
>> the computer as a pointer: it contains the address of a routine
>> intended to service an interrupt. There are 255 such vectors in
>> the PC, occupying the memory from 0000:0000 through 0000:03FF.
>> Eight of these (sixteen in the AT) are reserved for use by the
>> hardware Interrupt ReQuest lines, or IRQs. When an enabled
>> interrupt occurs, the PC stops what it's doing and executes the
>> routine whose address is stored in the appropriate vector.
Next most complicated is the chained software interrupt. One
example of an existing software interrupt is the BIOS disk service,
which uses INT 13H. If you wanted your handler to be invoked whenever
disk activity occurred, you'd chain onto this interrupt vector and
monitor the registers using MASM. When an event of interest occurred,
you'd "CALL SetUEvent" to notify QB. In any case, you'd pass the
interrupt along to the original INT 13H handler. Closely related to
this is the chained hardware interrupt. The setup is exactly the
same: hook the interrupt vector, monitor the registers, etc. All
other details are taken care of by an existing handler.
The code in CHNEVENT.BAS is an example of a chained handler which
will work for any hardware or software interrupt. The assumption is
that you're just monitoring existing events (and sometimes activating
SetUEvent), but not touching any hardware. In the example we monitor
INT 9, the keyboard interrupt, but you can monitor almost any of
the256 vectors by replacing "9" with the appropriate number. Try an
experiment: replace the INKEY loop with a LINE INPUT statement. If
you can explain what happens, you've grasped the essentials of QBevent
handling.
>> "Hooking" a vector means only that you store the address of your
>> own service routine in the vector. To facilitate cleanup, it's
>> usual to first retrieve and store the existing contents of the
>> vector so that they can be replaced on exit. If you're "chaining"
>> onto this vector, then you'll also use that original address when
>> your routine is finished, by performing a JMP directly to it.
>> Since this can happen to several routines in sequence, it's easy
>> to see why it's known as chaining.
The next step up in complexity (and it's a pretty big step) is
the exclusive hardware interrupt. Here, you're responsible for all of
the nitty-gritty of the PC hardware, in addition to any details
associated with the hardware device. You must program the 8259A
Programmable Interrupt Controller to allow interrupts on your IRQ
level, then issue a command to clear the PIC when you service an
interrupt. These must be done in MASM, as your QB event handler will
not be executed in a timely fashion and so cannot be relied on to take
care of these high-speed events. The code in EVENTHDW.ASM shows how
to deal with an event occurring on an arbitrary IRQ line (determined
at install time), but because we aren't dealing with a real device
The QBNews Page 24
Volume 1, Number 3 May 22, 1990
here, the specific instructions for the interrupting hardware can only
be hinted at.
>> Each hardware IRQ line is intimately tied to a vector: in the
>> case of the lower IRQs (0-7) the INT number (the vector number) is
>> simply the IRQ number plus 8. That's why the KB interrupt, which
>> uses IRQ 1, is vectored through INT 9.
Slightly more complicated is the shared hardware interrupt. In
order for two hardware devices to share an IRQ line, there must be
away to determine which device requested service by interrupting. An
example of sharing an interrupt might be COM1 and COM3, which both use
IRQ4 and hence INT 0CH. When an interrupt occurs on IRQ4, the COM3
service routine gains control and examines a register in the UART it's
responsible for to see if that UART caused the interrupt. If it
didn't, control is passed to the COM1 service routine. I haven't
included a specific example of adding a shared handler, but if you
need one and can't figure it out from the code shown, you can contact
me and I'll try to help.
In addition to the above, whenever you take over an interrupt
vector you must somehow put things back in order when your program
terminates. At a minimum this means restoring the original contents
of the vector; for hardware interrupts, you must also restore the
8259A Interrupt Mask Register bit to the state in which you found it.
To make this process a bit more automatic, QB includes the
B_OnExit routine. Any running BC program takes over a number of
interrupt vectors for its own use (for example, BC math functions
invoke INT 04H whenever an overflow occurs) which must be restored on
any exit, normal or abnormal. BC and QB provide B_OnExit as an
extension of this internal cleanup. You still must write the code to
do the actual restoring of vectors, etc., but BC can call that code
automatically on *any* exit, even an error crash, if you "register
"your routine via B_OnExit. Each of the included code packages uses
B_OnExit in this way.
**********************************************************************
Jim Mack is a programmer specializing in real-time systems for the
entertainment industry. He can be reached via CIS ID 76630,2012 on
the MSSYS forum in the BASIC or MASM sections, or at Editing Services
Co., PO Box 599, Plymouth MI 48170, (313) 459-4618
**********************************************************************
[EDITORS NOTE]
For some reason, the program CHNEVENT.BAS will cause my computer
to crash when run in the QB 4.5 enviroment. This is not the case when
run in the QBX (BC7) enviroment. Caution is advised if you try to run
this in the enviroment. Source code for this article is contained in
UEVENT.ZIP.