[comp.sys.ibm.pc] Ctrl-C trapping -- a hideous MS-DOS "feature"

nather@ut-sally.UUCP (Ed Nather) (02/01/88)

There are several ways to trap a "Ctrl-C" or "Ctrl-Break" keystroke
if you have cleanup to do before your program exits.  I include one
way in the attached sample program, but I have tried the interrupt
intercept as described in Norton's "Programmers' Guide to the IBM
PC" as well.  Both fail with the code below.  Ctrl-C is NOT trapped
by either method.  (Both Microsoft C v4.0 and Turbo C v1.0 behave
the same way; I'm sure MS-DOS is the culprit, since the "kbhit()"
routine just accesses the MS-DOS interrupt 06h).

It appears that the "Direct Console Interrupt 06 hex" has a wired-in
trap for "Ctrl-C."  If that code arrives and then 06h is used to
ask if a keystroke is waiting, the program is immediately aborted.
I cannot find this behavior documented anywhere.  It is a nifty
MS-DOS "feature" that will blow your program out of the water without
warning.  Beware.

(I had to write an assembly-code keyboard intercept program just to
stop Ctrl-C, so if I sound irritable, that's why.)
--------------------------------------------------------------------

#include <stdio.h>
#include <dos.h>

main()
{
set_raw();                           /* set MS-DOS to raw mode */
printf("Hit Ctrl-C twice.\n");
if(!kbhit())                         /* if no keystroke waiting */
    ;                                /* do nothing */
bdos(7,0,0);                         /* wait for a keystroke */
if(!kbhit())                         /* do it again ... */
    ;
bdos(7,0,0);
printf("ok so far, now once more ...\n");
while(!kbhit())
    ;                                /* watch until a keystroke appears */
bdos(7,0,0);                         /* try to get it */
printf("Never gets here.\n");
restore_raw();
exit(0);
}

/* Following code is by Dan Kegel, author of "nansi.sys" */

/* Use the IOCTL DOS function call to change stdin and stdout to raw mode.
 * For stdin, this prevents MSDOS from trapping ^P, ^S, ^C, thus freeing us
 * of ^P toggling 'echo to printer'.
 * For stdout, this radically speeds up the output because there is no
 * checking for these special characters in the input buffer whenever
 * screen output is occurring.
 * Note that only the stdin OR stdout ioctl need be changed since
 * apparently they are handled as the same device.
 * Thanks to Mark Zbikowski (markz@microsoft.UUCP) for helping me with
 * this.
 * --- This stolen from sources to the mighty game HACK ---
 */
#define DEVICE		0x80
#define RAW		0x20
#define IOCTL		0x44
#define STDIN		fileno(stdin)
#define STDOUT		fileno(stdout)
#define GETBITS		0
#define SETBITS		1
static unsigned	old_stdin, old_stdout, ioctl();
/*--- set_raw() ----------
  Call this to set raw mode; call restore_raw() later to restore
  console to old rawness state.
--------------------------*/
set_raw()
{
	old_stdin = ioctl(STDIN, GETBITS, 0);
	old_stdout = ioctl(STDOUT, GETBITS, 0);
	if (old_stdin & DEVICE)
		ioctl(STDIN, SETBITS, old_stdin | RAW);
	if (old_stdout & DEVICE)
		ioctl(STDOUT, SETBITS, old_stdout | RAW);
}
restore_raw()
{
	if (old_stdin)
		(void) ioctl(STDIN, SETBITS, old_stdin);
	if (old_stdout)
		(void) ioctl(STDOUT, SETBITS, old_stdout);
}
static unsigned
ioctl(handle, mode, setvalue)
unsigned setvalue;
{
	union REGS regs;

	regs.h.ah = IOCTL;
	regs.h.al = mode;
	regs.x.bx = handle;
	regs.h.dl = setvalue;
	regs.h.dh = 0;			/* Zero out dh */
	intdos(&regs, &regs);
	return (regs.x.dx);
}
/*-- end of setraw.msc --*/
-- 
Ed Nather
Astronomy Dept, U of Texas @ Austin
{allegra,ihnp4}!{noao,ut-sally}!utastro!nather
nather@astro.AS.UTEXAS.EDU

ray@micomvax.UUCP (Ray Dunn) (02/26/88)

In article <10277@ut-sally.UUCP> nather@ut-sally.UUCP (Ed Nather) writes:
>There are several ways to trap a "Ctrl-C" or "Ctrl-Break" keystroke
>if you have cleanup to do before your program exits.  I include one
>way in the attached sample program, but I have tried the interrupt
>intercept as described in Norton's "Programmers' Guide to the IBM
>PC" as well.  Both fail with the code below.  Ctrl-C is NOT trapped
>by either method.  (Both Microsoft C v4.0 and Turbo C v1.0 behave
>the same way; I'm sure MS-DOS is the culprit.....

Using Microsoft C v3.0 and 4.0 I have *NEVER* had any problems trapping ^C
using the "int (*signal(sig,func))()" feature with the manifest constant
SIGINT, eg:

    previous_ctrl_c_handler = signal(SIGINT, my_ctrl_c_handler);

This works perfectly, whether I want my_ctrl_c_handler to exit the program,
to continue after some processing (including reading input), or even to
redefine the ctrl C handler then continue.

RTFM.

This seems significantly simpler than the stuff Ed published & CERTAINLY
does not require assembler!

Ray Dunn.  ..philabs!micomvax!ray