qc.hlp (Table of Contents; Topic list)
Important Notice
The pages on this site contain documentation for very old MS-DOS software, purely for historical purposes. If you're looking for up-to-date documentation, particularly for programming, you should not rely on the information found here, as it will be woefully out of date.
ALARM.C
                                             Up Contents Index Back
────────────────────────────────────────────────────────────────────────────
 
/* ALARM.C illustrates in-line assembly and functions or keywords
 * related to terminate-and-stay-resident programs. Functions include:
 *      _dos_setvect    _dos_getvect    _dos_keep
 *      _enable         _disable        _chain_intr
 *
 * Keywords:
 *      _interrupt      _asm
 * Directive:
 *      #pragma
 * Pragma:
 *      check_stack     check_pointer   intrinsic
 * Global variables:
 *      _psp
 *
 * WARNING: You should run ALARM from the DOS command line. Running from
 * inside the QuickC environment will cause subsequent memory problems.
 *
 * See MOVEMEM.C for another pragma example.
 */
 
#include <dos.h>
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
/* Stack and pointer checking off */
#pragma check_stack( off )
#pragma check_pointer( off )
#pragma intrinsic( _enable, _disable )
 
/* In-line assembler macro to sound a bell. Note that comments in macros must
 * be in the C format, not the assembler format.
 */
#define BEEP() _asm { \
                        _asm     sub  bx, bx    /* Page 0          */ \
                        _asm     mov  ax, 0E07h /* TTY bell        */ \
                        _asm     int  10h       /* BIOS 10         */ \
                    }
 
#define TICKPERMIN  1092L
#define MINPERHOUR  60L
enum BOOLEAN { FALSE, TRUE };
 
/* Prototypes for interrupt functions */
void (_interrupt _far *oldtimer)( void );
void (_interrupt _far *oldvideo)( void );
void _interrupt _far newtimer( void );
void _interrupt _far newvideo( unsigned _es, unsigned _ds, unsigned _di,
                               unsigned _si, unsigned _bp, unsigned _sp,
                               unsigned _bx, unsigned _dx, unsigned _cx,
                               unsigned _ax, unsigned _ip, unsigned _cs,
                               unsigned _flags );
 
/* Variables that will be accessed inside TSR must be global. */
int  ftimesup = FALSE, finvideo = FALSE;
long goaltick;
long _far *pcurtick = (long _far *)0x0000046cL;
 
/* Huge pointers force compiler to do segment arithmetic for us. */
char _huge *tsrstack;
char _huge *appstack;
char _huge *tsrbottom;
 
void main( int argc, char **argv )
{
    long minute, hour;
    unsigned tsrsize;
 
    /* Initialize stack and bottom of program. */
    _asm mov  WORD PTR tsrstack[0], sp
    _asm mov  WORD PTR tsrstack[2], ss
    FP_SEG( tsrbottom ) = _psp;
    FP_OFF( tsrbottom ) = 0;
 
    /* Program size is:
     *     top of stack
     *   - bottom of program (converted to paragraphs)
     *   + one extra paragraph
     */
    tsrsize = ((tsrstack - tsrbottom) >> 4) + 1;
 
    /* If command line not given, show syntax and quit. */
    if( argc < 2 )
    {
        puts( "  Syntax: ALARM <hhmm> " );
        puts( "     where <hhmm> is time (military format) to ring alarm" );
        exit( 1 );
    }
 
    /* Convert time to ticks past midnight. Time must include 0 in first
     * position (0930, not 930). Time must be later than current time.
     */
    minute = atol( argv[1] + 2 );
    argv[1][2] = 0;
    hour = atol( argv[1] );
    goaltick = (hour * MINPERHOUR * TICKPERMIN) + (minute * TICKPERMIN);
    if( *pcurtick > goaltick )
    {
        puts( "It's past that time now" );
        exit( 1 );
    }
 
    /* Replace existing timer and video routines with ours. */
    oldtimer = _dos_getvect( 0x1c );
    _dos_setvect( 0x1c, newtimer );
    oldvideo = _dos_getvect( 0x10 );
    _dos_setvect( 0x10, newvideo );
 
    /* Free the PSP segment and terminate with program resident. */
    _dos_freemem( _psp );
    _dos_keep( 0, tsrsize );
}
 
/* Our timer interrupt compares current time to goal. If earlier,
 * it just continues. If later, it beeps and sets a flag to quit checking.
 */
void _interrupt _far newtimer()
{
    if( ftimesup )
        _chain_intr( oldtimer );
    else
    {
        /* First, execute the original timer interrupt. */
        (*oldtimer)();
 
        /* Activate if two conditions are met: First, it's past time for
         * the alarm. Second, we are not in a video interrupt. Checking
         * the video interrupt prevents the rare but potentially
         * dangerous case of calling INT 10 to beep while INT 10 is
         * already running.
         */
        if( (*pcurtick > goaltick) && !finvideo )
        {
            /* Set flag so we'll never return. */
            ftimesup = TRUE;
 
            /* Save current stack of application, and set old stack of TSR.
             * This is for safety since we don't know the state of the
             * application stack, but we do know the state of our own stack.
             * Turn off interrupts during the stack switch.
             */
            _disable();
            _asm
            {
                mov  WORD PTR appstack[0], sp   ; Save current stack
                mov  WORD PTR appstack[2], ss
                mov  sp, WORD PTR tsrstack[0]   ; Load new stack
                mov  ss, WORD PTR tsrstack[2]
            }
            _enable();
 
            BEEP();
            BEEP();
            BEEP();
 
            /* Restore application stack. */
            _disable();
            _asm
            {
                mov  sp, WORD PTR appstack[0]
                mov  ss, WORD PTR appstack[2]
            }
            _enable();
        }
    }
}
 
/* Protects against reentering INT 10 while it is already executing.
 * Although rare, this could be disastrous if the interrupt routine was
 * interrupted while it was accessing a hardware register.
 */
void _interrupt _far newvideo( unsigned _es, unsigned _ds, unsigned _di,
                               unsigned _si, unsigned _bp, unsigned _sp,
                               unsigned _bx, unsigned _dx, unsigned _cx,
                               unsigned _ax, unsigned _ip, unsigned _cs,
                               unsigned _flags )
{
    static unsigned save_bp;
 
    /* If not already inside interrupt, chain to original. */
    if( !finvideo )
        _chain_intr( oldvideo );
    else
    {
 
        /* Set the inside flag, then make sure all the real registers
         * that might be passed to an interrupt 10h match the parameter
         * registers. Some of the real registers may be modified by the
         * preceding code. Note that BP must be saved in a static (nonstack)
         * variable so that it can be retrieved without modifying the stack.
         */
        ++finvideo;
        _asm
        {
            mov ax, _ax
            mov bx, _bx
            mov cx, _cx
            mov dx, _dx
            mov es, _es
            mov di, _di
            mov save_bp, bp
            mov bp, _bp
        }
 
        /* Call the original interrupt. */
        (*oldvideo)();
 
        /* Make sure that any values returned in real registers by the
         * interrupt are updated in the parameter registers. Reset the flag.
         */
        _asm
        {
            mov bp, save_bp
            mov _bp, bp
            mov _di, di
            mov _es, es
            mov _dx, dx
            mov _cx, cx
            mov _bx, bx
            mov _ax, ax
        }
        --finvideo;
    }
}