Hi, This is a sample TSR which uses the F1 key as a hotkey. Compile this under BCC Run it once, it loads itself into memory. Run it again, it unloads itself. Press F1 while it is loaded to get a message. The only problem with it is, some programs bypass the DOS input loop and use the BIOS. You can get around this by intercepting int 15H, function 90h, subfunction 2 instead of int28. David Lindauer, gclind01@ulkyvx.louisville.edu -------------------------------------------------------------------- /* * TSR.C * * Author: David Lindauer 6/6/95 * * Code is specific to Borland C on an AT, SMALL memory model * * This code is a sample TSR which intercepts the F1 key and prints * 'F1 pressed' on the display. It is a fully functional TSR capable * of unloading itself. Run it once, it loads. Run it again, it unloads * * Compiled using BCC 4.5 * I set my editor to a tab setting of 2 to create this file */ #include #include #include /* A boolean type */ typedef short BOOL; #define TRUE 1 #define FALSE 0 /* This magic number is derived from the number of pushes the * INTERRUPT keyword forces. It indicates the position relative to * BP where the flags were saved during an INT call. This will * be compiler-dependent!!! */ #define MagicNumber 22 int far *InDos; /* *InDos is true if in dos, can't make recursive DOS calls*/ /* Reference only, I don't use it */ unsigned mypsp; /* Program Segment Prefix of the TSR */ unsigned sstos, sptos; /* TOP of TSR stack will be saved here */ unsigned sssave,spsave;/* Caller's stack will be saved here during int calls*/ BOOL Activate = FALSE; /* Set true by the INT15 handler when TSR activates */ /* We are going to save interrupt vectors here and restore them when the * TSR is unloaded. Note that if another TSR is loaded after this one * the unload will wipe out its hooks */ void interrupt (*old15)(); void interrupt (*old28)(); void interrupt (*old80)(); /* Prototypes */ void Cleanup(void); void hotkey_routine(void); void SetPsp(unsigned psp); unsigned GetPsp(void); void Deallocate(void); /* Int 80 handler, used for communication between one instance o the * TSR and another. When TSR loads it calls this interrupt with function=0 * to see if it has already been loaded. if so it calls this interrupt * with function 0x4a to force it to unload itself */ void interrupt catcher() { if (_AH == 0) { /* Function 0 just sets a flag telling the caller we exist */ int far *p; /* Caller provides a pointer to the flag in ES:DI */ p = MK_FP(_ES,_DI); *p = TRUE; } else if (_AH == 0x4a) { /* Function 0x4a makes the TSR unload itself */ unsigned oldpsp; /* Getting the PSP of the caller here */ sssave = _SS; /* Switch to local stack. Must because spsave = _SP; * Libraries assume SS = DS */ asm cli _SS = sstos; _SP = sptos; asm sti oldpsp = GetPsp(); /* Get caller PSP */ setvect(0x28,old28); /* Unhook the interrupt vectors */ setvect(0x80,old80); setvect(0x15,old15); SetPsp(mypsp); /* Switch to local PSP */ Cleanup(); /* Call local cleanup routine */ Deallocate(); /* Deallocate memory used by TSR */ SetPsp(oldpsp); /* Switch back to caller PSP */ asm cli /* Switch back to caller stack */ _SS = sssave; _SP = spsave; asm sti } } /* * Int 28h handler, used by DOS to tell TSRs it is idling in an input * loop. If we are in a DOS input loop and the int 15h handler has * detected an active condition we call the hotkey routine */ void interrupt DOSOk() { if (Activate) { Activate = FALSE; hotkey_routine(); } asm pushf /* We have to chain to the old int 28h handler */ asm call old28 /* in case there are any more ISRs */ } /* Int 15h handler. Int 15h is really for multitasking but the int 9 * handler calls it so it can translate keys. If we clear the carry * before returning the key will be ignored by the bios */ void interrupt kbhandler() { asm pushf if (_AH == 0x4f) { /* AH MUST be 0x4f to indicate this is a key * translation request */ if (_AL == 0x3b) { /* AL is the scan code. Can be got via * bios KB functions */ Activate = TRUE ; /* Tell the idle interrupt we have a hotkey */ /* The next line locates the CARRY flag where it was stored on the * stack on the INT call and clears it. We have to clear the * stacked version because IRET will restore flags from the stack */ *((unsigned char *)(_BP) + MagicNumber) &= 0xfe; asm popf goto gotkey; } } asm popf asm pushf /* If we didn't process the key or not a key chain to */ asm call old15 /* next int 15h routine in case it is handled there */ asm lahf /* Store the result flags on the stack */ *((unsigned char *)(_BP) + MagicNumber) = _AH; gotkey: } /* Deallocate memory resources used by the TSR */ void Deallocate(void) { union REGS regs; struct SREGS sregs; regs.h.ah = 0x49; /* First deallocate the TSR program */ sregs.es = mypsp; int86x(0x21,®s,®s,&sregs); regs.h.ah = 0x49; /* Now deallocate the TSR environment */ sregs.es = *(unsigned far *)(MK_FP(mypsp,0x2c)); int86x(0x21,®s,®s,&sregs); } /* Get PSP, get the program segment prefix of the executing program. * We need to switch to the TSR psp while running the TSR because * some DOS functions (notably file I/O) depend on having the * proper PSP set */ unsigned GetPsp(void) { union REGS regs; regs.h.ah = 0x51; int86(0x21,®s,®s); return(regs.x.bx); } /* Set PSP back to what it was */ void SetPsp(unsigned val) { union REGS regs; regs.h.ah = 0x50; regs.x.bx = val; int86(0x21,®s,®s); } /* Get INDOS flag. We don't use it, this is just an example. * You would use it to tell whether you were inside a DOS function * before you call a DOS function. Dos is not reentrant so you can't * do that */ int far *GetInDos(void) { union REGS regs; struct SREGS sregs; regs.h.ah = 0x34; int86x(0x21,®s,®s,&sregs); return( MK_FP(sregs.es, regs.x.bx)); } /* * Call int 80 to see if TSR already exists */ int catchint(void) { BOOL exist = FALSE; union REGS regs; struct SREGS sregs; regs.h.ah = 0; /* Function 0 = set flag if exist */ regs.x.di = FP_OFF(&exist); /* Address of flag */ sregs.es = FP_SEG(&exist); if (getvect(0x80)) /* If there is an int vector 0x80 */ int86x(0x80,®s,®s,&sregs); /* Call it */ return(exist); } /* * Call int 80 to unload an existing TSR * assumes the TSR exists */ void unload(void) { union REGS regs; regs.h.ah = 0x4a; /* Function 0x4a = unload */ int86(0x80,®s,®s); } /* * Routine to exit the TSR and leave it resident. * Another MAGIC routine... it assumes the stack segment comes last * It also assumes the _SP to be a maximum of 65534, which may * or may not be the case depending on the model used. It seems true * on the SMALL model * keep basically does an int21, func 0x31 */ void doKeep(void) { unsigned sofs = (unsigned)(65536L >> 4); keep(0, sofs + _SS - mypsp ); } /* * Cleanup routine, you would flush file buffers, close files, deallocate * memory, call exit routines, etc. */ void Cleanup(void) { } /* * Hotkey routine. Sets us to the TSR context, applies the TSR * hotkey function, resets us to the caller context */ void hotkey_routine(void) { unsigned oldpsp; sssave = _SS; /* Set to the TSR local stack */ spsave = _SP; asm cli _SS = sstos; _SP = sptos; asm sti oldpsp = GetPsp(); /* Get caller PSP for later */ SetPsp(mypsp); /* Set to local PSP */ /* Now we do the hotkey function */ printf("F1 pressed\n"); /* Now we set back to caller context */ SetPsp(oldpsp); /* Caller PSP */ asm cli /* Caller Stack */ _SS = sssave; _SP = spsave; asm sti } /* * TSR main routine. Note that the exit routine is never reached, * therefore a C program which is a TSR will never do nice things like * automatically close files or release memory. You must do it explicitly * in the cleanup routine */ void main(void) { /* If TSR exists unload it and exit */ if (catchint()) { printf("Unloading TSR\n"); unload(); exit(0); } /* Save the old interrupts so we can chain and later unhook */ old28 = getvect(0x28); old15 = getvect(0x15); old80 = getvect(0x80); /* Get InDos flag and local PSP */ InDos = GetInDos(); mypsp = GetPsp(); /* hook all the interrupts */ setvect(0x28,DOSOk); setvect(0x80,catcher); setvect(0x15,kbhandler); /* Initialize stack for switch to local */ sstos = _SS; sptos = _SP; printf("Loading TSR\n"); /* Exit to DOS, keeping the program */ doKeep(); }