home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Monster Media 1994 #1
/
monster.zip
/
monster
/
PROG_BAS
/
PBFIXES2.ZIP
/
README.TXT
< prev
next >
Wrap
Text File
|
1994-01-11
|
10KB
|
194 lines
How to handle Control-Break and Control-C in a PowerBASIC program
By Ray Crumrine
Control-C and Control-Break are strange beasts. PowerBASIC
allows you to specify $OPTION CNTLBREAK OFF in your program to
disable Control-Break checking. A piece of literature I read
somewhere about QuickBASIC said "pressing Control-C never
interrupts a BASIC program". Neither statement is completely
true.
If you disable Control-Break checking with $OPTION CNTLBREAK OFF,
the resulting compiled program will not be stopped when the user
presses Control-Break but when your program ends, DOS will
display ^C and newline after the DOS prompt is displayed. The
DOS programmers reference, 2nd edition states this is because
pressing Control-Break "forces a Ctrl-C character into the
keyboard buffer that DOS maintains (separate from the one
maintained by BIOS). DOS then discovers that Ctrl-C the next time
it checks for a character" (after your program exits, the first
time the DOS prompt is displayed). While this is only cosmetic
and really does no harm, it makes your program look like it has a
bug in it.
The problem with Control-C is a more subtle, but insidious one.
The real fault here may lie with PowerBASIC, but I am not sure.
DOS allows its operation to be aborted at times if the user
presses Ctrl-C. This may be OK or even desirable when one is
working at the DOS prompt, but since most work done with a PC is
done while using an external program (such as yours) it is
imperative that you not allow DOS to shut down your program.
This is because when DOS shuts down, interrupt vectors "hooked"
by PowerBASIC will be left "hanging" and the computer will crash
completely very soon thereafter.
Did you ever look at your DOS manual and see a reference to the
BREAK command? It is a little used command that nobody pays much
attention to. Simply put, if BREAK=OFF then DOS only checks to
see if Ctrl-C has been pressed when it writes to the screen or
when it is waiting for input from the keyboard. If BREAK=ON then
DOS checks to see if the user has pressed Ctrl-C ALL of the time.
BREAK defaults to OFF unless you have the BREAK=ON command in
either your CONFIG.SYS file or AUTOEXEC.BAT file. It can also be
turned off/on by any program that runs on your PC.
Having said all that, let me explain the two problems with Ctrl-C
that can affect your PowerBASIC programs. The first is simple.
If BREAK=ON then your PowerBASIC program will display ^C and
unceremoniously crash if the user presses Ctrl-C while reading
from or writing to the disk.
The second problem occurs when your program wants to use DOS for
console output. You might want to do this to access the ANSI.SYS
driver. You use the statement OPEN "CONS:" FOR OUTPUT AS #1 (or
whatever file number you want to use) to do this. Then when you
want to print to the screen instead of using PRINT "Hello" you
use PRINT #1, "Hello". PowerBASIC sends the string "Hello" to
DOS and DOS does the actual printing to the screen. If the user
presses Ctrl-C while DOS is writing to the screen, again you get
^C displayed on the screen and your program crashes. It does not
matter whether BREAK=ON or BREAK=OFF in this case.
There appear to me to be two ways to prevent these errors. The
first would be to write your own Int 9 (keyboard) interrupt
handler and prevent DOS from ever finding out Ctrl-Break or Ctrl-
C was pressed. I suspect this is the method used by word
processors and other programs that need complete control of the
keyboard, but it is not easily accomplished and is probably
overkill for most programs. The second method involves either
"hooking" the Ctrl-C and Ctrl-Break interrupts so that code of
your own design runs when these two interrupts are called, or
"patching" the DOS interrupt code with an Iret instruction so
that when Int 1Bh or Int 23h is called, nothing happens. This is
the method I chose. Your program can still detect if the user
pressed either Ctrl-C or Ctrl-Break by using the INKEY$ function
provided by PowerBASIC. INKEY$ will return CHR$(0,0) if Ctrl-
Break was pressed and CHR$(3) if Ctrl-C was pressed.
There are two FUNCTIONS and one SUB in the file CTRLC.BAS.
----------
FUNCTION CbrkDisable%
This function requires no arguments and returns an integer
result. This should be one of the first executable statements in
your program if not THE first. The first thing it does is ask
DOS for the address of the current Ctrl-C handler. Then it saves
the FIRST byte found at that address, and replaces it with an
Iret instruction. This prevents DOS from taking control and
crashing our program if the user presses Ctrl-C.
Next we repeat the process for the Ctrl-Break vector. Doing this
prevents the ^C that would otherwise be displayed after our
program ends, if the user presses Ctrl-Break.
There are just three items left to be done. If the user presses
Ctrl-C, DOS can STILL trash your screen by displaying ^C on the
screen wherever the cursor is currently positioned. There is NO
way to prevent this, but we can minimize the likelihood that it
will happen by setting the DOS BREAK flag OFF. You may remember
that when BREAK=OFF DOS only checks for Ctrl-C during screen
output and keyboard input. Since PowerBASIC does not use DOS for
keyboard input, it can never trash our screen UNLESS we use
OPEN "CONS:" FOR OUTPUT AS #1 as described above which implies
that we WANT to give give DOS a chance to intercept Ctrl-C. So
the third item of business for CbrkDisable is to ask DOS what the
current state of the BREAK flag is and save it so we can restore
it when our program is done, and then we set BREAK=OFF using a
DOS call.
The next item of business is to ask DOS for the address of the
current Critical error handler. We save the segment address in
the SHARED variable CEHSeg?? for use by the ClrErDev routine
(described below).
The last thing CbrkDisable does is call the PowerBASIC routine
SetOnExit and pass it the address of our routine CbrkRestore.
PowerBASIC can be instructed to call as many as eight separate
user procedures just before the program is exited. By having
SetOnExit call CbrkRestore for us, we ensure that DOS will be
restored to its original state even if PowerBASIC shuts down
prematurely due to an unexpected error. The procedure is added
to the list, and a true/false integer value is returned in ax to
reflect the success of the operation. A false value indicates
that eight procedures have already been defined. The value in ax
is returned by CbrkDisable using the result% integer as an
intermediary. Note that PowerBASIC allows you to CALL a FUNCTION
just like a SUB if you are not interested in the return value.
This is true of CbrkDisable.
----------
SUB CbrkRestore
This is a PRIVATE routine known only in this module. We can do
this because this sub will only be called by SetOnExit, and
PowerBASIC accesses it by address. By making it private it can
co-exist with another routine with the same name without any
conflict.
This routine undoes the changes that CbrkDisable made during
startup of the program by in effect POKEing the two bytes back
into the Int 1Bh and Int 23h interrupt routines and restoring the
DOS BREAK flag to its original state, thus restoring DOS to its
original state completely.
----------
FUNCTION CritErr%()
This function gives you a "hook" into the PowerBASIC V3.0c critical error
handling system. PowerBASICs ERDEV function can be used after CALL
INTERRUPT to determine if a DOS critical error occurred. However, there
are a couple of problems with the way ERDEV works. The most significant
is the fact that PowerBASIC does not provide a way to clear ERDEV once an
error has occurred. The second one is mostly a matter of personal
preference, and that is the error codes returned by ERDEV are not
converted to match the error numbers that are returned by DOS Int 21h,
function 59h (Get extended error).
Function CritErr% does three things:
1: Returns logical TRUE (-1) if a critical error occurred during the DOS
call or FALSE (0) if there was none.
2: If there was an error, I add 12h to the flag value and store the
number back into PowerBASICs ERDEV variable location. This allows you
to simply use ERDEV to retrieve the error code. Making the error
value match Int 21h function 59h makes for cleaner error handling in
your program and easier generation of error messages.
3: Lastly, if there was an error I clear the PowerBASIC critical error
flag for the next CALL INTERRUPT.
CbrkDisable MUST be called before CritErr can be used, otherwise the
computer will probably CRASH! Here is the syntax for using CritErr%
DECLARE FUNCTION CbrkDisable%()
DECLARE FUNCTION CritErr%()
'Call CbrkDisable FIRST
success% = CbrkDisable% 'If you want to know if SetOnExit worked
or
CALL CbrkDisable 'If you're sure it did
CALL INTERRUPT &h21
IF CritErr% THEN
CriticalErrNum% = ERDEV
END IF
I can be reached on the PowerBASIC BBS or at
(217) 223-8767 evenings
(217) 221-6194 business