qp.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.PAS
  Example Contents Index                                    Back
 
PROGRAM alarm;
 
{ ALARM.PAS illustrates in-line assembly and routines related
  to Terminate-and-Stay-Resident programs, including:
 
      GetIntVec    Keep    SetIntVec
 
  Keywords illustrated include:
 
      INLINE    INTERRUPT
 
  WARNING:  You must run ALARM from the DOS command line. The
  QuickPascal environment does not permit TSRs to be installed from
  inside it, since this would cause subsequent memory problems.
}
 
USES
    Crt, Dos;
 
{ Keep memory sizes minimal to reduce the amount of memory that will be
  made resident. Notice that no heap is requested, since the program
  never uses one. Notice, also, that stack checking is not needed.
}
{$M $1000, 0, 0 }
{$S-}
 
CONST
    tick_per_min = 1092;
    min_per_hour = 60;
    DING = 392;    { G }
    DONG = 296;    { D }
 
    { Typed constant. }
    chk_time : Boolean = True;
 
VAR
    { Variables that will be accessed inside TSR must be global. }
    segTsrStack, offTsrStack : Word;
    segAppStack, offAppStack : Word;
    goal_tick, minute, hour  : Longint;
    dummy                    : Integer;
    old_timer                : POINTER;
 
PROCEDURE call_int( vector : POINTER );
BEGIN
    INLINE
        (
        $9C/           { pushf           ; Push flags and clear }
        $FA/           { cli             ; ints to simulate INT }
        $FF/$5E/$04    { call  [bp+4]    ; Call the old vector  }
        );
END; { procedure call_int }
 
{============================== new_timer ==============================
  Our timer interrupt compares current time to goal. If earlier, it just
  continues. If later, it beeps and sets a flag to quit checking.
}
 
PROCEDURE new_timer; INTERRUPT;
BEGIN
 
    { Call the original timer interrupt to do its work. }
    call_int( old_timer );
 
    { If time is up, we do our work. }
    IF (chk_time) AND (MemL[$40:$6C] > goal_tick) THEN
        BEGIN
 
        { If time is up, set flag so we'll never return. }
        chk_time := FALSE;
 
        { 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.
        }
        INLINE
            (
            $FA/                { cli                                  }
            $89/$26/offAppStack/{ mov offAppStack, sp ; Save app stack }
            $8C/$16/segAppStack/{ mov segAppStack, ss                  }
            $8B/$26/offTsrStack/{ mov sp, offTsrStack ; Load TST stack }
            $8E/$16/segTsrStack/{ mov ss, segTsrStack                  }
            $FB                 { sti                                  }
            );
 
        { Now do the TSR task. At this point you can use any function or
          procedure that accesses memory or hardware directly. The
          Sound, NoSound, and Delay procedures qualify. To use DOS and
          BIOS interrupts safely, you must use additional techniques
          which are not explained here.
        }
        Sound( DING );
        Delay( 750 );
        Sound( DONG );
        Delay( 500 );
        NoSound;
 
        { Restore application stack. }
        INLINE
            (
            $FA/                    { cli                  }
            $8B/$26/offAppStack/    { mov  sp, offAppStack }
            $8E/$16/segAppStack/    { mov  ss, segAppStack }
            $FB                     { sti                  }
            );
 
        END;
 
END; { procedure new_video }
 
{============================ main program ============================}
BEGIN
 
    { Save stack of TSR for later use. }
    INLINE
        (
        $89/$26/offTsrStack/    { mov  offTsrStack, sp }
        $8C/$16/segTsrStack     { mov  offTsrStack, ss }
        );
 
    { If no command line parameters, show syntax and quit. }
    IF (ParamCount <> 1) THEN
        BEGIN
        Writeln( 'Syntax: ALARM <hhmm> ' );
        Writeln( '  where <hhmm> is 24-hour format time to ring alarm' );
        Halt( 1 );
        END;
 
    { Convert time to ticks past midnight. }
    Val( ParamStr( 1 ), minute, dummy );
    minute := minute MOD 100;
    Val( ParamStr( 1 ), hour, dummy );
    minute := minute + ((hour DIV 100) * min_per_hour);
    goal_tick := minute * tick_per_min;
 
    { Make sure time isn't already past. }
    IF (MemL[$40:$6C] > goal_tick) THEN
        BEGIN
        Writeln( 'It''s past that time now.' );
        Halt( 1 );
        END;
 
    { Replace existing timer routine with ours. }
    GetIntVec( $1c, old_timer );
    SetIntVec( $1c, @new_timer );
 
    { Terminate with program resident. }
    Keep( 0 );
 
END.