(***********************************************************************

Name:           CtlCTrap.Pas
Version:        1.0


ABSTRACT:

   Provides reliable CTRL/C trapping for TURBO Pascal programs  running
   under MS-DOS and PC-DOS.

ENVIRONMENT:

   MS-DOS or PC-DOS, compiled with TURBO Pascal.

   Tested combinations:

       MS-DOS V2.11 with TURBO Pascal V3.01A (MS-DOS generic)
       MS-DOS V2.11 with TURBO Pascal V3.02A (MS-DOS generic)
       PC-DOS V2.10 with TURBO Pascal V3.00B (PC-DOS specific)
       PC-DOS V2.10 with TURBO Pascal V3.01A (PC-DOS specific)
       PC-DOS V2.10 with TURBO Pascal V3.02A (MS-DOS generic)
       PC-DOS V2.10 with TURBO Pascal V3.02A (PC-DOS specific)

AUTHOR:

   Brian Hetrick

EDIT HISTORY:

       Brian Hetrick, 12 December 1986: Version 1.0
 000 - Original creation of module.

ACKNOWLEDGMENTS:

   This is an enhanced version of the CTRLC.PAS public domain  program,
   discovered  on  the  MARKET public access bulletin board, author un-
   known.

***********************************************************************)
{.PA}
(*
*  TYPE DECLARATIONS:
*)

TYPE

   CtrlCPtr = ^ CHAR;

(*
*  CONSTANT DECLARATIONS:
*)

CONST

   CtrlCCount : INTEGER = 0;
   CtrlCFlag  : BOOLEAN = FALSE;

(*
*  VARIABLE DECLARATIONS:
*)

VAR

   CtrlCVect  : CtrlCPtr;
{.PA}
PROCEDURE CtrlCHandler;

(***********************************************************************

FUNCTIONAL DESCRIPTION:

   THIS ROUTINE MUST NOT BE CALLED BY THE CLIENT PROGRAM.  THIS ROUTINE
   MUST BE DEFINED AT THE OUTERMOST LEVEL (i.e., not nested inside  an-
   other routine).

   Control/C interrupt handler.  Called by MS-DOS when a  Control/C  is
   detected in the input stream.  Sets the Control/C flag and dismisses
   the Control/C interrupt.

   Ray Duncan's book "Advanced MS-DOS" documents the  possible  actions
   that could be taken by a Control/C handler as any one of:

    o  Take any appropriate action and execute  an  IRET.   The  MS-DOS
       function in progress will be restarted and return normally.
    o  Take any appropriate action and execute a far RETURN.  If  carry
       is  set,  MS-DOS  will abort the application and otherwise "will
       continue in the normal manner".
    o  Keep control and never return.

   The first alternative is chosen here.  Although any MS-DOS  function
   call  can by used an a Control/C interrupt handler, a TURBO function
   may have been occurring and so no TURBO functions may be used.

FORMAL PARAMETERS:

   None.

RETURN VALUE:

   None.

IMPLICIT INPUTS:

   None.

IMPLICIT OUTPUTS:

   CtrlCFlag - The Control/C flag.

SIDE EFFECTS:

   None.

***********************************************************************)

   BEGIN

   (*
    *  Standard TURBO procedure entry for  routines  at  the  outermost
    *  level.  A different entry sequence is used for routines that are
    *  within other routines (it  must  ensure  addressability  of  the
    *  outer routine's variables), and this other sequence is NOT legal
    *  for interrupt routines
    *)

                               {         PUSH    BP                   }
                               {         MOV     BP,SP                }
                               {         PUSH    BP                   }
                               {         JMP     procedure body       }

   (*
    *  Recommended TURBO interrupt procedure entry
    *)

   InLine ($50/                {         PUSH    AX                   }
           $53/                {         PUSH    BX                   }
           $51/                {         PUSH    CX                   }
           $52/                {         PUSH    DX                   }
           $56/                {         PUSH    SI                   }
           $57/                {         PUSH    DI                   }
           $1E/                {         PUSH    DS                   }
           $06/                {         PUSH    ES                   }
           $FB);               {         STI                          }

   (*
    *  Note CTRL/C occurrence.  As the data segment is not  addressable
    *  at this point, only CONST variables may be used
    *)

   CtrlCFlag := TRUE;

   (*
    *  Recommended TURBO interrupt procedure exit
    *)

   InLine ($07/                {         POP     ES                   }
           $1F/                {         POP     DS                   }
           $5F/                {         POP     DI                   }
           $5E/                {         POP     SI                   }
           $5A/                {         POP     DX                   }
           $59/                {         POP     CX                   }
           $5B/                {         POP     BX                   }
           $58/                {         POP     AX                   }
           $8B/$E5/            {         MOV     SP,BP                }
           $5D/                {         POP     BP                   }
           $CF)                {         IRET                         }

   END;
{.PA}
FUNCTION CtrlCOccurred : BOOLEAN;

(***********************************************************************

FUNCTIONAL DESCRIPTION:

   Tests whether a Control/C has occurred.  This function MUST be  used
   rather  than just checking the CtrlCFlag variable, as Control/Cs are
   detected only at MS-DOS function calls.

FORMAL PARAMETERS:

   None.

RETURN VALUE:

   TRUE - There is an unhandled Ctrl/C present.
   FALSE - There is no unhandled Ctrl/C present.

IMPLICIT INPUTS:

   CtrlCCount - The count of outstanding enables of the Control/C pack-
       age.
   CtrlCFlag - The Control/C pending flag.

IMPLICIT OUTPUTS:

   CtrlCFlag - The Control/C pending flag.

SIDE EFFECTS:

   May issue an MS-DOS function call permitting Control/C detection.

***********************************************************************)

   TYPE

       RegisterPackage = RECORD
           AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags : INTEGER
           END;

   VAR

       Registers : RegisterPackage;

   BEGIN

   (*
    *  If the Control/C package is  not  initialized,  then  it  cannot
    *  detect Control/Cs.
    *)

   IF CtrlCCount = 0
   THEN

       CtrlCOccurred := FALSE

   ELSE
       BEGIN

       (*
        *  If there is no Control/C pending, issue MS-DOS function call
        *)

       IF NOT CtrlCFlag
       THEN
           BEGIN

           Registers . AX := $0B00;
           MsDos (Registers)

           END;

       (*
        *  Return Control/C status and reset pending flag
        *)

       CtrlCOccurred := CtrlCFlag;
       CtrlCFlag := FALSE;

       END
   END;
{.PA}
PROCEDURE CtrlCSetup;

(***********************************************************************

FUNCTIONAL DESCRIPTION:

   Initializes the Control/C package.

FORMAL PARAMETERS:

   None.

RETURN VALUE:

   None.

IMPLICIT INPUTS:

   CtrlCCount - The count of outstanding enables of the Control/C pack-
       age.
   Interrupt vector 23 (Control/C trap).

IMPLICIT OUTPUTS:

   CtrlCCount - The count of outstanding enables of the Control/C pack-
       age.
   Interrupt vector 23 (Control/C trap).
   CtrlCVect - The original Control/C trap vector.

SIDE EFFECTS:

   None.

***********************************************************************)

   TYPE

       RegisterPackage = RECORD
           AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags : INTEGER
           END;

   VAR

       Registers : RegisterPackage;

   BEGIN

   (*
    *  If necessary, set up the Control/C vector
    *)

   IF CtrlCCount = 0
   THEN
       BEGIN

       (*
        *  Initialize the CtrlCFlag
        *)

       CtrlCFlag := FALSE;

       (*
        *  Save the old Control/C vector
        *)

       Registers . AX := $3523;
       MsDos (Registers);
       CtrlCVect := Ptr (Registers . ES, Registers . BX);

       (*
        *  Install the new Control/C vector
        *)

       Registers . AX := $2523;
       Registers . DS := Cseg;
       Registers . DX := Ofs (CtrlCHandler);
       MsDos (Registers)

       END;

   (*
    *  Increment the installation count
    *)

   CtrlCCount := CtrlCCount + 1

   END;
{.PA}
PROCEDURE CtrlCTearDown;

(***********************************************************************

FUNCTIONAL DESCRIPTION:

   Tears down the Control/C package.

FORMAL PARAMETERS:

   None.

RETURN VALUE:

   None.

IMPLICIT INPUTS:

   CtrlCCount - The count of outstanding enables of the Control/C pack-
       age.
   CtrlCVect - The original Control/C trap vector.

IMPLICIT OUTPUTS:

   CtrlCCount - The count of outstanding enables of the Control/C pack-
       age.
   Interrupt vector 23 (Control/C trap).

SIDE EFFECTS:

   None.

***********************************************************************)

   TYPE

       RegisterPackage = RECORD
           AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags : INTEGER
           END;

   VAR

       Registers : RegisterPackage;

   BEGIN

   (*
    *  Decrement the installation count
    *)

   CtrlCCount := CtrlCCount - 1;

   (*
    *  If necessary, remove handler
    *)

   IF CtrlCCount = 0
   THEN
       BEGIN

       (*
        *  Restore old Control/C routine
        *)

       Registers . AX := $2523;
       Registers . DS := Seg (CtrlCVect ^);
       Registers . DX := Ofs (CtrlCVect ^);
       MsDos (Registers);

       (*
        *  Ignore any Control/Cs that were captured
        *)

       CtrlCCount := 0

       END
   END;