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.