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