     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
