                           PC INTERRUPTS & C

                            Matthew Probert
                           Servile  Software


The IBM PC BIOS and DOS contain functions that may be called by a 
program by way of the function's interrupt number. The address of the 
function assigned to each interrupt is recorded in a table in RAM 
called the interrupt vector table. By changing the address of an 
interrupt vector a program can effectively disable the original 
interrupt function and divert any calls to it to its own function. 
This was done by the critical error handler described in the section 
on error handling. 

Borland's Turbo C provides two library functions for reading and 
changing an interrupt vector. These are: setvect() and getvect(). The 
corresponding Microsoft C library functions are: _dos_getvect() and 
_dos_setvect(). 

getvect() has the function prototype;

        void interrupt(*getvect(int interrupt_no))();


setvect() has the prototype;

        void setvect(int interrupt_no, void interrupt(*func)());

To read and save the address of an existing interrupt a program uses 
getvect() thus; 


/* Declare variable to record old interrupt */
void interrupt(*old)(void);

main()
{
    /* get old interrupt vector */
    old = getvect(0x1C);
    .
    .
    .
}

Where 0x1C is the interrupt vector to be retrieved.

To then set the interrupt vector to a new address, our own function, 
we use setvect() thus; 

void interrupt new(void)
{
    .
    .
    /* New interrupt function */
    .
    .
    .
}

main()
{
    .
    .
    .
    setvect(0x1C,new);
    .
    .
    .
    .
}

There are two important points to note about interrupts; 

First, if the interrupt is called by external events then before 
changing the vector you MUST disable the interrupt callers using 
disable() and then re-enable the interrupts after the vector has been 
changed using enable(). If a call is made to the interrupt while the 
vector is being changed ANYTHING could happen! 

Secondly, before your program terminates and returns to DOS you must 
reset any changed interrupt vectors! The exception to this is the 
critical error handler interrupt vector that is restored automatically 
by DOS, so your program needn't bother restoring it. 

This example program fragment hooks the PC clock timer interrupt to provide a 
background clock process while the rest of the program continues to run. If 
included with your own program that requires a constantly displayed clock on 
screen, you need only amend the display coordinates in the call to puttext(). 
Sincle the clock display code is called by a hardware issued interrupt, your 
program can start the clock and forget it until it terminates. 
 
/* Compile in LARGE memory model */

#include <stdio.h>  
#include <dos.h>    
#include <time.h>   
#include <conio.h>  
#include <stdlib.h> 

enum { FALSE, TRUE };

#define COLOUR    (BLUE << 4) | YELLOW

#define BIOS_TIMER    0x1C

static unsigned installed = FALSE;
static void interrupt (*old_tick) (void);

static void interrupt tick (void)
{
    int i;
    struct tm *now;
    time_t this_time;
    char time_buf[9];
    static time_t last_time = 0L;
    static char video_buf[20] =
    {
        ' ', COLOUR, '0', COLOUR, '0', COLOUR, ':', COLOUR, '0', COLOUR,
        '0', COLOUR, ':', COLOUR, '0', COLOUR, '0', COLOUR, ' ', COLOUR
    };

    enable ();

    if (time (&this_time) != last_time)
    {
        last_time = this_time;

        now = localtime(&this_time);

        sprintf(time_buf, "%02d:%02d.%02d",
                now->tm_hour,now->tm_min,now->tm_sec);

        for (i = 0; i < 8; i++)
        {
            video_buf[(i + 1) << 1] = time_buf[i];
        }

        puttext (71, 1, 80, 1, video_buf);
    }

    old_tick ();
}


void stop_clock (void)
{
    if (installed)
    {
        setvect (BIOS_TIMER, old_tick);
        installed = FALSE;
    }
}

void start_clock (void)
{
    static unsigned first_time = TRUE;

    if (!installed)
    {
        if (first_time)
        {
            /* atexit() declares a function which will be called 
               before the program terminates, either through an exit()
               function call, or the end of function main()
            */
            atexit (stop_clock);
            first_time = FALSE;
        }

        old_tick = getvect (BIOS_TIMER);
        setvect (BIOS_TIMER, tick);
        installed = TRUE;
    }
}

