Chapter 12
Event and error trapping
PowerBASIC contains background processing code, for event trapping, and error trapping, and will generate code in your programs, to make use of this already-working background code.
Event trapping means that you can write interrupt-driven programs, where an interrupt generated by an external event can activate your program, and you can let PowerBASIC do the checking work for you, in its background level.
In between every line of your foreground program, PowerBASIC will generate code to invoke the background portion of its runtime library, which will go to check whatever you have told it to check, like your "watch dog".
This is an alternative to creating your own popup (TSR) programs.
All of the ON <....> statements are event trapping. ON UEVENT gives you the capability to create user-defined event trapping, for unusual types of interrupts. This is another way that PowerBASIC is user-extendible, and customizable.
This makes the compiled output for your program a little bigger, three bytes between every line of your code. Your program will run a little slower, but if you have an 80386 or 80486, you will probably not even notice.
Most commonly, this will be checking the keyboard, ON KEY, to see if a particular key or key combination has been pressed.
You can trap for 11 keys, KEY 15 thru 25, using KEY n CHR$, then with POPUP KEY, you can do more and different keys.
More advanced programmers can utilize the other events.
Event trapping
ON COMMANDS
ON COM
ON ERROR
ON <value> GOSUB
ON <value> GOTO
ON KEY
ON PEN
ON PLAY
ON STRIG
ON TIMER
ON UEVENT
ON COM statement
Purpose: Declares the trap subroutine for serial port events.
Syntax: ON COM({ 1 | 2 | 3 | 4 }) GOSUB { label | line number }
1, 2, 3, or 4 specifies which communications adapter (serial port) is to be read.
label or line number identifies the trap subroutine.
If line number is zero, trapping is disabled.
The ON COM(n) statement has no effect until event trapping is enabled for a given serial port by an appropriate COM(n) ON statement.
Once the COM(n) ON statement has been executed, PowerBASIC checks before the execution of every subsequent program statement to see if a character has arrived at the specified serial port.
If one has, a GOSUB is performed to the designated subroutine.
ON COM(n) GOSUB Label
ON COM(n) GOSUB LineNumber
ON COM(n) GOSUB 0
COM statement
Purpose: Enables or disables trapping of serial communications port events.
Syntax: COM({ 1 | 2 | 3 | 4 }) {ON|OFF|STOP}
1, 2, 3, or 4 is the number of the communications adapter to be trapped.
COM(n) ON enables checking of port n.
After COM(n) ON has been executed, PowerBASIC checks
the indicated communications port before the execution
of each program statement to see if any characters have
arrived.
If so, control passes to the subroutine specified in the
most recently executed ON COM(n) GOSUB statement.
COM(n) OFF disables checking of port n; any activity is ignored.
COM(n) STOP also disables checking at port n, but remembers any activity so that an immediate trap occurs if COM(n) ON is later executed.
ON <VALUE> GOSUB
Purpose: Calls one of several subroutines according to the value of a numeric expression.
Syntax: ON n GOSUB { label | line number } [, { label | line number }]...
n is a numeric expression ranging from 0 to 255, and each label or line number identifies a statement to branch to. When this statement is encountered, the nth label in the list is branched to; a subsequent RETURN returns control to the statement following ON GOSUB. If n is 0 or greater than the number of labels, no branch occurs.
ON <VALUE> GOTO
Purpose: Sends program flow to one of several possible destinations based on the value of a numeric expression.
Syntax: ON n GOTO { label | line number } [, { label | line number }]...
n is a numeric expression ranging from 0 to 255, and each label or line number identifies a statement to branch to. When this statement is encountered, the nth label in the list is branched to. If n is 0 or greater than the number of labels, no branch occurs.
ON KEY statement
Purpose: Declares the trap subroutine to get control if a specific key is pressed.
Syntax: ON KEY(n) GOSUB { label | line number }
n is an integer expression which specifies the key to be trapped
label or line number identifies the first statement of the trap
subroutine.
The ON KEY statement has no effect until event trapping is enabled for a
given key by an appropriate KEY(n) ON statement.
KEY statement
Purpose: Enables or disables trapping for a particular keyboard character
Syntax: KEY(n}) {ON|OFF|STOP}
KEY(n) ON enables checking of port n.
After KEY(n) ON has been executed, PowerBASIC checks
the indicated communications port before the execution
of each program statement to see if the specified key has
been pressed.
If so, control passes to the subroutine specified in the
most recently executed ON KEY(n) GOSUB statement.
KEY(n) OFF disables checking of port n; any activity is ignored.
KEY(n) STOP also disables checking at port n, but remembers any activity
so that an immediate trap occurs if KEY(n) ON is later
executed.
When the specified key has been pressed, an implicit KEY(n) STOP statement
is executed to prevent the trap subroutine from being called recursively
from within itself. The closing RETURN of the handling subroutine
performs an implicit KEY(n) ON unless the trap routine executes an
explicit KEY(n) OFF statement.
*********************> [ p.278 ] what happened to KEY 26 thru 29 ????
Table 12.1
-----------------------------------------------------------------------
Single keys Combination keys
Built-in User-defineable
KEY 1 F1 KEY 15
KEY 2 F2 KEY 16
KEY 3 F3 KEY 17
KEY 4 F4 KEY 18
KEY 5 F5 KEY 19
KEY 6 F6 KEY 20
KEY 7 F7 KEY 21
KEY 8 F8 KEY 22
KEY 9 F9 KEY 23
KEY 10 F10 KEY 24
KEY 11 UpArrow KEY 25
KEY 12 LeftArrow
KEY 13 RightArrow
KEY 14 DownArrow
KEY 30 F11
KEY 31 F12
---------------------------------------------------------------------------
You can custom-define key combinations that will be associated with KEYs 15 thru 25. See Programmer's Guide, Appendix A, Table A.3 and Table A.4 for details.
You can associate your own functions and subroutines with all of these special purpose keys.
Some examples of key combinations that you might want to trap for, to keep control over the machine, no matter what the user tries to do.
Ctrl-Break
Ctrl-C
Ctrl-Alt-Del
rem this one traps for Ctrl-Alt-Del
KEY 15, CHR$(&H0C,&H53,&H03)
ON KEY(15) Gosub TrapWarmBoot
KEY(15) ON
ON PEN STATEMENT
Purpose: Declares the trap subroutine to get control when light-pen activity occurs.
Syntax: ON PEN GOSUB { Label | Line number }
label or line number identifies the first statement of the trap subroutine. The ON PEN statement has no effect until event trapping is enabled for the light pen by PEN ON . Once PEN ON has been executed, PowerBASIC checks before the execution of every subsequent program statement to see if light pen activity has occurred. If it has, a GOSUB is performed to the designated subroutine.
When light pen activity occurs, an implicit PEN STOP statement is executed to prevent the trap subroutine from being called recursively from within itself. The closing RETURN of the handling subroutine performs an implicit PEN ON unless the trap routine executes an explicit PEN OFF statement.
PEN statement
Purpose: Controls the checking of light-pen events.
Syntax: PEN { ON | OFF | STOP }
PEN ON enables trapping of light-pen events by the routine indicated in an ON PEN statement.
PEN OFF disables the PEN function and turns off pen event checking.
PEN STOP turns off pen event trapping, but remembers pen events so that if PEN ON is later issued, a trap occurs immediately.
ON PLAY statement
Purpose: Declares the trap subroutine to get control if the background music buffer note count falls below a certain number.
Syntax: ON PLAY(notecount) GOSUB { label | line number }
label or line number identifies the first statement of the trap subroutine to be called if less than notecount notes remain in the background music buffer. The ON PLAY statement has no effect until event trapping is enabled by PLAY ON . Once PLAY ON has been executed, PowerBASIC checks before the execution of every subsequent program statement to see if notecount has been reached. If it has, a GOSUB is performed to the
designated subroutine. The number of notes in the music buffer must be greater than notecount before PLAY ON is executed, otherwise this event will never be triggered.
PLAY statement
Purpose: Creates music.
Syntax: PLAY string expression
PLAY { ON | OFF | STOP }
string expression is made up of interpreted music notes grouped with other music commands. notes are specified like this: note-letter [{#|+|-}]
See ************* for details on actual use of this command to make music.
The second form of the PLAY statement (PLAY { ON | OFF | STOP }) is used to control event trapping when the number of notes in the background music buffer falls below a certain value. PLAY ON turns on this type of event trapping.
The ON PLAY(n) statement is used to set the number of notes which cause this event to be triggered.
PLAY ON enables a check, before the execution of every statement, to see if the note count has fallen below the trigger value; if it has, program execution is diverted to the routine specified in the ON PLAY(n) statement.
PLAY OFF disables this type of event trapping.
PLAY STOP also disables trapping, but remembers whether the note count has been triggered in the meantime; if a PLAY ON statement is subsequently executed, a trap occurs immediately.
ON STRIG statement
Purpose: Declares the trap subroutine to get control when a joystick button is pressed.
Syntax: ON STRIG(n) GOSUB { label | line number }
label or line number identifies the first statement of the trap subroutine. n is an integer expression indicating the button to be trapped: 0 for button 1 on joystick A, 2 for button 1 on joystick B, 4 for button 2 on joystick A, or 6 for button 2 on joystick B.
The ON STRIG(n) statement has no effect until event trapping is enabled for a joystick button by a STRIG(n) ON statement.
Once a STRIG(n) ON has been executed, PowerBASIC checks before the execution of every subsequent program statement to see if the joystick button has been pressed.
If it has, a GOSUB is performed to the designated subroutine.
When a joystick button trap occurs, an implicit STRIG(n) STOP statement is executed to prevent the trap subroutine from being called recursively from within itself.
The closing RETURN of the handling subroutine performs an implicit STRIG(n) ON unless the trap routine executes an explicit STRIG(n) OFF statement.
STRIG statement
Purpose: Controls joystick button trapping.
Syntax: STRIG { ON | OFF }
STRIG(n) { ON | OFF }
STRIG ON turns on trigger-event trapping so that STRIG function requests can be made. Trapping is performed by the routine specified in an ON STRIG statement. STRIG OFF turns off trigger-event checking.
STRIG(n) ON enables checking for the joystick button specified by n
0 =joystick A, button 1; 2 = joystick B, button 1;
4 = joystick A, button 2; 6 = joystick B, button 2
before every subsequent statement. If the button is pressed, control passes to the statement specified in an appropriate ON STRIG(n) statement.
STRING(n) OFF Joystick event trapping is turned off. Any joystick events after this statement are forgotten.
STRIG function
Purpose: Returns the status of the joystick buttons.
Syntax: y = STRIG(option)
option is an integer expression that controls the result returned:
See ************* for details on actual use of this command to check status of the joystick buttons.
Before making any STRIG function calls, you must enable joystick button checking with the STRIG statement. Use the STICK function to read the position of the joystick itself.
STICK function
Purpose: Returns joystick position information.
Syntax: y = STICK(option)
Returns the information specified by the integer expression option:
0 = returns x coordinate for joystick A
1 = returns y coordinate for joystick A
2 = returns x coordinate for joystick B
3 = returns y coordinate for joystick B
You must execute STICK(0) to trigger the reading process before you can read either dimension of either joystick.
ON TIMER statement
Purpose: Declares the trap subroutine to get control every n seconds.
Syntax: ON TIMER(n) GOSUB { label | line number }
label or line number identifies the first statement of the timer trap routine, and n is an integer expression indicating the number of seconds to wait, from 1 to 86400 (24 hours).
The ON TIMER statement has no effect until time checking is enabled by TIMER ON . Once TIMER ON has been executed, an internal count of seconds begins, and a check is made before the execution of every subsequent program statement to see if the indicated number of seconds have elapsed. If so, a GOSUB is performed to the designated subroutine.
When the subroutine gets control, an implicit TIMER STOP statement is executed to prevent the trap subroutine from being called recursively from within itself.
The closing RETURN of the handling subroutine performs an implicit TIMER ON unless the trap routine executes an explicit TIMER OFF statement.
Thus the seconds count is reset to zero at the end of a timer trap.
TIMER statement
Purpose: Controls trapping of timer events.
Syntax: TIMER { ON | OFF | STOP }
TIMER ON turns on timer-event trapping, so that control is passed to the subroutine specified in a previous ON TIMER(n) statement after n seconds have elapsed. TIMER OFF turns off timer-event trapping. TIMER STOP also turns off timer-event trapping, but timer events are remembered so that if TIMER ON is later issued, a trap occurs immediately.
ON UEVENT statement
Purpose: Declares the subroutine to get control when a user- defined event occurs.
Syntax: ON UEVENT GOSUB { label | line number }
You must enable user-defined event trapping with UEVENT ON .
You must also either $LINK "UEVENT.PBU", or write your own assembly language interrupt routine. You can do the same thing with a POPUP MULTIPLEX, or a POPUP INTERRUPT program.
UEVENT statement
UEVENT ON turns on user event trapping so that control is passed to the subroutine specified in a previous ON UEVENT statement when that user defined event occurs.
UEVENT OFF turns off user-event trapping.
UEVENT STOP also turns off user-event trapping, but timer events are remembered, so that if a UEVENT ON is later issued, a trap occurs immediately.
DECLARE SUB SetUevent()
This procedure is called to inform the run-time library that a user defined event (UEVENT) has occurred. If ON UEVENT is active, the specified subroutine will be executed at the first opportunity.
MISCELLANEOUS COMPILE TIME
EVENT OR ERROR COMMANDS
$EVENT ON will start the compiler generating event-checking code until it sees an $EVENT OFF command.
$EVENT OFF will stop the compiler from generating the code untill it sees an
$EVENT ON command again.
$ERROR BOUNDS will enable generation of array bounds checking code
$ERROR NUMERIC will enable generation of floating point
divide-by-zero and overflow checking code
$ERROR OVERFLOW will enable generation of integer overflow checking code
$ERROR STACK will enable generation of stack overflow checking code upon entry to each subroutine function, or procedure.
$ERROR ALL will enable generation of error-checking code
It is a good idea to compile with $ERROR ALL ON, during testing, then compile with $ERROR ALL OFF, when you are satisfied with the program.
PowerBASIC automatically generates event-trapping code whenever it encounters any instructions that need it.
$EVENT metastatement
If your program contains an event trap of some sort, for example, ON KEY,
ON COM(n), then the compiler default is to generate event checking code. This code causes the program during execution, to check for the event(s) being trapped between every statement.
If your program doesn't do trapping, then $EVENT is automatically set to OFF and no event checking code is generated.
To make your programs smaller, you can gain power over this event-checking code by use of the $EVENT statement.
Include an $EVENT OFF statement at the beginning of your program, and then use an $EVENT ON statement shortly before you actually need event-checking program calls generated, and another $EVENT OFF afterwards, to minimize this type of extra code generation.
COMPILER EVENT-CHECKING
CODE GENERATION
If your program contains an ON UEVENT GOTO statement anywhere, the compiler will generate this event checking code, all throughout your program, even though it is only at risk for events thru a small section of code.
The $EVENT commands give you the power to control whether and where PowerBASIC will generate different types of runtime event-checking code, to minimize the size of the compiled .EXE file output.
$EVENT { ON | OFF | + | - }
These are your choices.
1. event checking code all thru your program.
$EVENT ON
2. No event checking code anywhere in your program.
$EVENT OFF
3. event checking code only thru a frequently called
portion of your program.
$EVENT OFF
...
$EVENT ON
...
Call GetKeyNoEcho
...
$EVENT OFF
...
...
Event-checking code slows execution slightly. $EVENT gives you control over what parts of your program will do event checking. If there is an area where maximum speed is important or you can be certain no events will occur, then bracket this code with $EVENT OFF and $EVENT ON statements.
Example
$LINK UEVENT.PBU
$EVENT ON
'controls compile-time generation of code
UEVENT ON
'controls run-time utilization of generated code
ON UEVENT Gosub Label
UEVENT OFF
'controls run-time utilization of generated code
$EVENT OFF
'controls compile-time generation of code
Label:
rem
rem
return
REM UEVENT.TIP
$IF 0
Here's a good example of how to use UEVENT. In this case, we will hook into the keyboard interrupt, INT 9, and set the UEVENT flag every time any key is pressed. Then, we'll let INT 9 take over like normal.
Every time a key is pressed, the ON UEVENT GOSUB code will execute. This is sort of like ON KEYBOARD GOSUB. For other examples of UEVENT, see the file UEVENT3.BAS, which is like ON PRINTSCREEN GOSUB, or INT1C.BAS, which can be adapted to set the UEVENT flag every clock tick (18 times per second) for a faster timer event than ON TIMER. -Erik
$END IF
DECLARE SUB SetUevent() 'PowerBASIC internal procedure must be declared
'locate our interrupt handler
SG??=CODESEG(INT9) ' INT9 is a label in this program
PR??=CODEPTR(INT9)
'Get the current vector of int 9
CALL GetInterruptVector(&H9,OSG??,OPR??) ' Get the OLD handler pointer
'Poke the old pointer as a hook into our asm routine
'POKE directly into the code segment! There's no other way to "hook"
'the interrupt that I can tell.
DEF SEG = CODESEG(OLDINT9)
POKE$ CODEPTR(OLDINT9),CHR$(&HEA)+MKWRD$(OPR??)+MKWRD$(OSG??)
DEF SEG
'Point the new INT 9 vector to our asm routine which calls SetUEvent
CALL SetInterruptVector(&H9,SG??,PR??)
'========[example]==========================================
' With INT9 now pointing to our own handler, ON UEVENT can be used to
' trigger a subroutine every time a key is pressed.
ON UEVENT GOSUB KeyPress
UEVENT ON
DO:LOOP ' loop forever. We can abort using our UEVENT trap.
' Don't forget to RESTORE THE INTERRUPT VECTOR before exiting the
' program or you'll have to reset (ctrl-alt-del won't even work!)
' In this example, we restore the interrupt and end the program
' when the user presses ESCAPE (see the next part of the program).
'========UEVENT =====================================
KeyPress:
SOUND 1000,.1 ' notice that for every key press, there are more than 1
' occurances of this trap. It's hard to tap a key so fast
' that it only runs this routine once!
A$=INKEY$ ' Get any key that might be in the keyboard buffer
' If ESC then exit: VERY IMPORTANT: You must RESTORE the
' OLD INTERRUPT VECTOR of INT 9 or your machine will lock
' up as soon as you exit to DOS!
IF A$=CHR$(27) THEN
CALL SetInterruptVector(&H9,OSG??,OPR??) 'restore old vector
END
ELSE
IF LEN(A$) THEN PRINT A$; ' print A$ if it's not null
END IF
RETURN
' ****************************************************************
'This is our "HOOK" which intercepts INT9, sets the UEVENT flag, and then
'JUMPS into the original INT 9 handler.
INT9:
! PUSH AX
! PUSH BX
! PUSH CX
! PUSH DX
! PUSH SI
! PUSH DI
! PUSH BP
! PUSH DS
! PUSH ES
! CALL far ptr SetUEvent
! POP ES
! POP DS
! POP BP
! POP DI
! POP SI
! POP DX
! POP CX
! POP BX
! POP AX
OLDINT9:
! DB &HEA ;long jump
! DB 0 ;insert pointer to long jump here
! DB 0 ;these bytes will be modified by the code at the
! DB 255 ;start of this program. 0 0 255 255 is a long jump
! DB 255 ;to the warm boot code in BIOS.
' Our code segment POKE above is what changes these "death" bytes into
' a healthy leap of faith into the default INT 9 handler.
' *********************************************************************
' Support Routines follow
SUB SetInterruptVector(Intr%,Segment??,Offset??)
REG 1,&H2500 + Intr%
REG 4,Offset??
REG 8,Segment??
CALL INTERRUPT &H21
END SUB
SUB GetInterruptVector(Intr%,S??,O??)
REG 1,&H3500 + Intr%
CALL INTERRUPT &H21
S??=REG(9)
O??=REG(2)
END SUB
An internal procedure
DECLARE SUB SetUevent()
This procedure is called to inform the run-time library that a user-defined event (UEVENT) has occurred. If ON UEVENT is active, the specified subroutien will be executed at the first opportunity.
Other ways to trap for events
You can trap for 11 keys, using KEY n CHR$, then with POPUP KEY, you can do more and different keys.
Using popup (TSR) programs, or assembly language programs linked to a particular hot key, or to a hardware or software interrupt.
With KEY n CHR$, and the associated KEY commands, the compiler generates three bytes between every line of your program. This makes your program compiled output bigger.
$EVENT OFF will stop the compiler from generating the code until it sees an $EVENT ON command again.