tsr.h
This chapter describes:- Capabilities and limitations of a Terminate and Stay Resident (TSR) program.
- How to use tsr_functions to write and debug a TSR program.
- TSR function reference
Note:
The tsr_functions are intended for use with real-mode
DOS. They are not supported when using the
16 or 32-bit DOS extender.
A running TSR program becomes a DOS extension and continuously monitors the keyboard for any key press that matches the key combination your program declares. This hotkey combination signals your program to spring to life, or pop up. A TSR can also be a background process, automatically invoked by the processor approximately 18 times every second.
DOS is a single tasking, single user operating system. However, DOS has hooks that TSRs can use. TSRs take over certain operating system functions and provide additional facilities without the operating system knowing. Some DOS utilities, including PRINT, DOSKEY, and FASTOPEN are TSRs that add capabilities to DOS when needed. New releases of DOS document new features.
Introduction
When you enter the name of a TSR program at the DOS prompt, it is treated like any other program. DOS will allocate memory for it and load the .exe or .com file. It will then pass control to the first instruction in the program. Since DOS is a single tasking operating system, the TSR begins execution with complete control over the PC.The PC has 256 interrupts (0 through 255), each with an associated vector in memory. A vector, consisting of four bytes, contains the far address of a routine to be performed when the associated interrupt occurs. So when you press a key, an interrupt 9 is generated. This results in the CPU freezing whatever it is doing, picking up the address held in the associated vector, and passing control to this routine. When this called routine returns the processor continues from where it left off.
A TSR usually intercepts several interrupts. If a TSR wants to monitor the keyboard or check for a special key combination, it can intercept interrupt 9 (INT 9). DOS calls INT 9 for every press or release of a key. To take advantage of the background scheduler, a TSR must also intercept interrupt 0x28 (INT 28).
Intercepting an interrupt consists of placing the address in a TSR program in the interrupt vector. If the vector for INT 9 is changed to point to a function in your program, that function will automatically be called every time someone presses a key. Once the TSR has processed the keystroke, it will need to call the function that had previously owned INT 9. When a TSR program is removed from memory, it should restore the original vector for INT 9.
Once a TSR has intercepted interrupts, it must determine the minimum amount of memory it requires. When a TSR terminates, it tells DOS how large it is, so that DOS can reserve that memory for the TSR before executing other programs. At this stage, the TSR is in a state of suspended animation. When an intercepted interrupt occurs, the program will activate.
TSR Capabilities
When a TSR is activated via one of the intercepted interrupts, it must first check to see if DOS and the BIOS are stable. Remember: DOS is a single user operating system. If DOS is doing one thing and a TSR pops up and asks it to do something different, the most likely result will be a frozen computer- or a trashed disk drive.This is overcome by the TSR program monitoring certain events. For instance, it monitors any non-re-entrant code and refuses to pop up when this is active. The TSR must monitor disk access and DOS operations to detect if they are in use.
The algorithm looks something like this:
is TSR is active? end else isDisk being used? end else is a graphics application running? end else if the program didn't get here via INT 28, is DOS busy? end
Most TSRs intercept the scheduler, INT 28, which is only called by DOS when DOS is not busy. For example, while DOS may be busy awaiting a keystroke at the prompt, it will call INT 28 periodically to let TSR programs operate. A TSR can use INT 28 as an "all clear" indicator; whenever a TSR responds to an INT 28, it can skip checking to see is DOS is busy. Chaining INT 28 guarantees that a TSR will get a chance at executing.
Once your TSR gets past those checks, it can continue as normal. The only catch: a TSR cannot call a DOS INT 21 service below 0Dh.
While TSR programming may seem complicated, tsr_functions allow you to ignore many of the details above when creating TSRs.
Compatible TSRs
Following these guidelines can make a TSR more compatible with DOS and other TSRs.
1. Intercept as few interrupts as possible.
2. Interrupt handlers must always service the old interrupt
routine first - unless they are adding features to an
existing service.
3. Do not restore interrupt vectors unless the TSR knows
that it is the owner of the vector. If a TSR owns a vector,
it should restore it to its original state before being
removed from memory.
Files for Building TSRs
tsr.hThis is the header file for tsr_functions. It must be included in your TSR program. It contains #defines for the scan codes and shift values that you can use when declaring your hotkey combination in your programs. It also contains the prototypes for the functions in the toolkit. Most importantly it ensures that the correct memory allocation method is used by the compiler. Failure to use this header file could result in your program taking far more memory than it actually needs.
resdemo.c
This file contains a sample program that you may wish to use for
reference when writing your own programs. It shows exactly how to
declare the necessary hot key and fingerprint declarations. It makes
use of many standard library functions, including the disp_functions.
tsrclock.c
This sample file program uses the background option of the tsr_
functions. Background mode is explained in detail later.
Writing a TSR Program
Write and debug a potential TSR program as a normal program. Do not make it memory resident until you are confident it is bug free.While writing your program keep the following points in mind:
- Do NOT use functions that allocate memory (such as malloc). You can use the page_functions to manage memory if you wish.
- Do NOT use buffered file (* FILE) routines; instead, use the untranslated functions (open/ read/ write/ close).
- Do NOT exit from any function, simply return.
Making Your Program Resident
If you examine the resdemo. c source code you will see that this program starts in exactly the same way as any other program. The main function is entered and the program examines the command line arguments. If none are supplied it presumes you are attempting to load the software in memory resident mode. So the software attempts to make itself resident by calling:
tsr_install(int argument)If this is successful, the function will NOT return. If it does return it will pass back an error code. Remember that you do not have to make your programs immediately become memory resident; selecting a menu item might tell a program to become a TSR.
The function tsr_install takes one argument; which can have
one of two values: POPONLY and TIMESLICE.
tsr_install(POPONLY);POPONLY converts the program to a pop-up TSR; the special function popmain is called only when the user presses the chosen hotkey combination. The alternative is:
tsr_install(TIMESLICE);Installed with TIMESLICE, your program will be converted into a background task and popmain will be called repeatedly - up to a maximum of 18 times per second. With this latter method, popmain will also be entered when the user presses the correct hotkey.
A program may need to determine whether popmain was entered through the hotkey or because of the timeslice algorithm. You can ascertain the answer to this by examining a global variable called _tsr_timeslice. If popmain() was entered by the timeslice algorithm, _tsr_timeslice will be set to 1; otherwise it will be set to 0. Examine the tsrclock.c source code to see this in action.
If you decide to write a background task (using TIMESLICE), you should design your program to be as efficient as possible. Try and keep the processing done in each time slice to a minimum.
As mentioned in the earlier introduction to memory resident programming you can make your program more compatible with other TSRs by giving them a chance to pop up when you are at a convenient point within your own TSR. You can do this with the tsr_service function.
void tsr_service(void)For instance, instead of waiting for a key press like this:
bioskey(0);Try this instead:
while(bioskey(1)==0) /* while no key press */ tsr_service(); /* give other TSRs a chance */ bioskey(0); /* then get key as normal */The function tsr_service simply fires off a scheduler interrupt (int 28h). No value is passed to or returned from tsr_service.
Debugging TSRs
When you enter the world of TSR programming, you have to accept that you can never do certain things and you can only do other things at certain times. Failure to adhere to these rules can result in a frozen computer. These problems can compound in a TSR written with C or C++ because you may know the rules, but a calling function might not, resulting in problems that are difficult to trace.To help you with debugging TSRs, we have included a facility that will trap and alert you to any possible bad practice within your TSR program. When you use this facility and any illegal actions are detected, a window will open with a (hopefully) meaningful message within it. This will help you to track down the particular function call that is causing the problems.
To switch this debugging aid on, simply add the command,
TSR_DEBUGto your existing tsr_install command. For instance, if you normally use the form:
tsr_install(POPONLY);Simply extend this to,
tsr_install(POPONLY| TSR_DEBUG);likewise you could use,
tsr_install(TIMESLICE| TSR_DEBUG);Note:
This facility does not attempt to cure any illegal actions; it only alerts the user (programmer), waits for a key press and then allows the request to continue as normal. It will not stop a faulty program from freezing the computer, but it will explain why the computer is about to freeze!
When invoked the debugging routines can trap several of the most
common pitfalls that you may encounter. For each different problem
you will see a meaningful message displayed:
Dos function 0dh Press a keyMeaning a function in your program called INT 21h (DOS function dispatcher) with the AH register set to a value below hexadecimal 0dh. This is illegal in a TSR program.
To cure this, you could place displays in your code to track down
the exact function call that caused the problem. Likely culprits are
the getch family.
Attempt to close std handle Press a keyEvery time you open a file, DOS allocates a handle to that file. Then when you want to read or write to it, you use the handle that DOS gave you on opening. The handles that DOS allocates start from 5 and increment with each open request. The handles from 0000 to 0004 are reserved by DOS for its standard devices. These are such things as keyboard/ screen/ printer and com port.
It is quite possible for your program to close these reserved devices,
either intentionally or by accident. If you do ask DOS to close one of
its standard handles, the debugging code will presume you have
done so in error and it will inform you accordingly.
Memory Allocation Not inside TSR!Memory resident programs are given a chunk of DOS's 640k when they make the transition from normal programs to TSRs. If they later make further requests for additional memory, DOS will try to oblige and get itself well and truly tangled. With this in mind the debugging software will watch for any attempts to get additional memory and the above window will appear to alert you of the request.
Note:
To dynamically manage memory within your TSR,
create a static buffer and convert it to a heap using
page_initialize. You can then use functions
such as page_malloc to manage memory. See
the page_functions for more details.
One additional error mesage might be observed:
Exit detected Use return insteadIn a normal program you probably used the exit function to abort your program. However, DOS does not really know about or understand TSR programs. It thinks only one program is running. It presumes the underlying application and your TSR are the same. Iif you end your TSR with a call to exit, DOS presumes the underlying program has asked to exit and will abort it. To avoid conflict, use return only; never use exit.
Removing a TSR from Memory
Notice in the resdemo. c source code, that if a /R is placed on the command line, the program attempts to unload a previously loaded copy of itself. It does this with a call to:int tsr_uninstall(void);This function always returns a value. This function can be called, either from within the TSR when it is active, or from a routine that is executed when your program is called from the DOS prompt. If called from within the pop up when active it removes the current copy of the TSR program from memory. Therefore, once the program has popped down it cannot pop up again. If called when the program is executed from DOS it removes any previously loaded copy of itself.
If you intend to remove your program from memory, when it is popped up, it is worth understanding how DOS allocates and de-allocates memory.
When a program is loaded by DOS, it is allocated one or more segments of memory. A segment is up to 64k bytes. A clever program can trace through the DOS allocated memory records and ascertain the owner of any segment (or part segment) of memory. When you use tsr_functions, your programs automatically have this ability and this is used when you try to remove your program from memory.
A call to tsr_uninstall, looks through the memory and returns to DOS any segments that have been allocated to your program. The call also unhooks interrupts used by the TSR routines. However just because the segments are returned to DOS does not mean your program is no longer in memory. It is and it will continue to run after tsr_uninstall returns. Although DOS now considers the memory previously allocated to your program to be free, in reality it still contains an image of your program, which is why it will continue to run. The freed blocks of memory will onlbe reused only when DOS needs to allocate memory for another program.
Consider the following situation:
1. You load your TSR program, DOS allocates memory to
it and returns to the DOS prompt.
2. You load an ordinary program, Wordstar for instance.
3. You pop up your program from within Wordstar and it
contains an option to remove itself from memory. (just
like the RESDEMO example)
4. You select the option to uninstall the pop up.
You have, in effect, created a hole in DOS memory because the
memory was allocated as follows:
DOS DRIVERS ... Your POP UP WordstarNow that DOS has regained the memory allocation blocks that were allocated to your pop up, a hole has appeared. However, DOS is capable of managing such situations. DOS will only use the memory in the hole if the memory is sufficient for its needs; DOS will not overwrite the application.
Finally when Wordstar is exited, DOS will regain all the memory associated with it and the hole will disappear. The return values from tsr_install and tsr_uninstall are as follows:
0 | Function successful |
1 | Can not load, program already loaded |
2 | Can not remove, the program is NOT loaded |
3 | Can not remove, another TSR program has been loaded on top of your program |
Global Variables
In your source file you must specify certain variables that the TSR routines can reference. The value you place in these variables determines how the TSR routines work.
HOTSHIFT and HOTSCAN
We have already stated that your (or any other) pop up TSR program
must have a special key sequence that it recognizes as the signal for
it to pop up. This is usually called the hotkey combination. It is
called a combination because it is the combination of one or more
shift keys and an ordinary key (usually in the range A-Z). When this
key combination is pressed the pop up will take control of the
machine resources and can run as if it was the only program in the
machine. To specify your hotkey combination in your programs is to
declare and initialize two variables called HOTSHIFT and HOTSCAN.
HOTSHIFT is an integer that must contain a value that represents the
shift keys you have chosen. To determine the value to place in this
integer, first choose your shift keys from one or more of the
following available keys:
LSHIFT | Left Shift key |
RSHIFT | Right Shift key |
CTRL | Control key |
ALT | Alt key |
int HOTSHIFT = ALT+RSHIFT;This declares your hot shift as being the alt key + the right shift key. You must also choose and declare the key that is to be used with the shift. Choose a key in the range A-Z and declare an int called HOTSCAN. Initialize it like this:
int HOTSCAN = SCAN_Q;This will declare your key to be Q, so when someone presses:
ALT+RIGHT SHIFT+QYour program will pop up. You must initialize HOTSCAN with a scan value not the character itself:
int HOTSCAN = 'Q'; /* WRONG! */ int HOTSCAN = SCAN_Q; /* RIGHT! */All the scan values for the keys A-Z and F1 to F10 are defined in the tsr. h file. If you really need to use a key outside the A-Z range, simply consult your favorite manual to find the scan value for the key you want to use and initialize HOTSCAN with this value.
If you prefer not to use a scan value and only want the hotkey to
consist of shift keys, declare HOTSCAN as:
HOTSCAN = NO_SCAN;This instructs TSR routines to ignore the scan value and test only the shift key values.
tsr_fprint
This character string is your program's unique identification, used by
tsr_install and tsr_uninstall to determine if your program
is loaded in memory. For example:
char tsr_fprint[20]= "Prog ID";Every time you write a new TSR program give it a unique ID.
_okbigbuf
The TSR routines have to determine how much memory your
program requires, so that they can free the remaining memory and
thus make it available to any applications that may be run. In order
for the routines to arrive at an optimum figure your program should
contain the following line above your main function:
extern int _okbigbuf = 0;Failure to do this will simply result in a TSR which takes up too much system memory.
_tsr_timeslice
As mentioned earlier, if you need to ascertain whether your
popmain was called because the hotkey was pressed or because of
the timeslice algorithm, you can use the global _tsr_timeslice.
If the hotkey was responsible this will be set to zero, if the algorithm was responsible it will be set to 1. Using this you can provide a background task that can still be popped up and configured in some way by the user. For example, examining the tsrclock.c source code will show that this is how it displays a clock on the screen and allows you to press the hotkey to toggle the display on or off.
The Special Function: popmain
When your hotkey combination is pressed the TSR routines will pass control to a function in your program called popmain. When writing this function, remember that at the point you are handed control of the computer, it is up to you to save any areas of screen that you may destroy. Also remember to save the cursor position and shape. The disp_function performs this.When you have completed processing, restore any areas of the screen that you may have overwritten and return control from popmain back to the calling TSR functions. They will return control to the underlying application.
Limitations
Programs that use the Digital Mars C++ TSR routines have the same limitations as any other TSR program. For example, you can only pop up when DOS is stable and no disk access is taking place. This is taken care of by the TSR routines. However no TSR program can allocate memory or make calls to DOS functions below 0Dh. An added problem is that you may use a library function to perform some invalid task and not know it.Library functions to avoid are:
- malloc (or any other memory related command)
- Any buffered file usage (fread, fgets and others).
Sample Program
The following is an example of the TIMESLICE facility. When you press the hotkey, the display's clock will toggle. This clock runs in the background while you continue to work in the foreground./* TSRCLOCK. C Demo program for Digital Mars' Memory Resident C/C++ Toolkit This is an example of the TIMESLICE facility of the TSR toolkit. If you press the hot key, you will toggle ON/OFF a clock on the screen. This clock will run in the background, while you continue to work as normal in the forground. */ #include <disp.h> #include <dos.h> /* All programs must have these statements */ #include <stdio.h> #include <tsr.h> /* must use this */ /* Your hotkey combination: */ unsigned TSR_HOTSHIFT = CTRL+ LSHIFT; char TSR_HOTSCAN = SCAN_Q; /* Unique string: */ char tsr_fprint[20] = "tsrclock.v1"; /* In addition, background programs must have this: */ extern unsigned _tsr_timeslice; /************************/ /*--Enter your program--*/ union REGS regs; int cur_pg, cur_s, cur_p; int toggle = 1; int hours, mins, secs; main(argc, argv) int argc; char **argv; { int i; if ((strncmp(argv[1],"/R", 2) == 0) || (strncmp(argv[1],"/r", 2)== 0)) { i = tsr_uninstall(); if(i == 0) printf("Program removed\n"); if(i == 2) printf("Can not remove, Program not loaded!\n"); if(i == 3) printf("Can not remove, Another program loaded above\n"); exit(0); } printf("Press Control+Left Shift+Q to toggle clock ON/OFF\n"); i= tsr_install(TIMESLICE+TSR_DEBUG); /* if it returns, error has occured */ if (i == 1) printf("Can not load, program already loaded!\n"); else printf("Failed to install, error %i\n", i); } void popmain(popmain) { /* POPMAIN is a special "reserved name", function to which the TSR routines will pass control when the hot key is pressed. */ if(_tsr_timeslice== 0)/* if hotkey*/ { toggle = toggle * -1;/* set toggle on/off*/ return; } if(toggle < 0)/* only display when on*/ return; regs.h.ah = 0x2c; intdos(®s, ®s); if( secs== regs.h.dh)/* and if secs changed*/ return; hours = regs.h.ch; mins = regs.h.cl; secs = regs.h.dh;/* save_cursor will destroy*/ save_cursor(); disp_open(); disp_move(0,66); disp_setattr(14); disp_printf("TIME:%2.2i:%2.2i:%2.2i", hours, mins, secs); disp_close(); restore_cursor(); } save_cursor() { regs.x.ax = 15 * 256; int86(0x10,®s,®s); cur_pg = regs.x.bx; regs.x.ax = 3 * 256; int86(0x10,®s,®s); cur_p = regs.x.dx; cur_s = regs.x.cx; regs.x.dx =(24 * 256) + 80; regs.x.ax = 2 * 256; regs.x.bx = cur_pg; int86(0x10,®s,®s); } restore_cursor() { regs.x.ax = 256; regs.x.bx = cur_pg; regs.x.cx = cur_s; int86(0x10,®s,®s); regs.x.dx = cur_p; regs.x.ax = 2 * 256; int86(0x10,®s,®s); }
Problems
The tsr_functions allow someone who has never heard of a "DOS BUSY FLAG" or even seen an assembler program to write pop ups easily and quickly. However, when you enter the world of the TSR you must expect problems. We have taken every care to ensure that self induced problems are kept to a minimum by trapping almost every action that you could inadvertently perform to crash your own TSR.If you experience problems with one of your programs, (most common will be a complete lock up), follow these steps to track the problem.
- Ensure that the debugging window does not open at any stage inside your TSR.
- Place displays in your program in order to identify the instruction or section of code that is causing problems.
- Check your own code thoroughly!
tsr_install
- Header
- tsr.h
- Prototype
- tsr_install(int argument);
- Description
- The tsr_install function makes a program memory-resident.
The argument must be either POPONLY or TIMESLICE.
POPONLY makes the program into a pop-up Terminate-and-Stay-Resident (TSR) utility; the special function popmain will only be called when the user presses the chosen hotkey combinations.
TIMESLICE makes the program into a background task and popmain is called repeatedly -to a maximum ot 18 times per second. In addition, popmain is entered when the user presses the correct hotkey.
- Return Value
- None, if successful. If an error occurs, an error code is returned.
- Compatibility
- DOS Small memory models only.
tsr_service
- Header
- tsr.h
- Prototype
- void tsr_service(void);
- Description
- The tsr_service function executes a scheduler interrupt (int 28h) that allows other Terminate-and-Stay-Resident (TSR) programs to pop up.
- Return Value
- None
- Compatibility
- DOS Small memory model only
tsr_uninstall
- Header
- tsr.h
- Prototype
- int tsr_uninstall(void);
- Description
- The tsr_uninstall function removes a Terminate-and-Stay-Resident (TSR) utility from memory. You can call this function from within a TSR when it is active or from a routine that is executed when your program is called from the DOS prompt. If tsr_uninstall is called from within the pop up when it is active, it removes the current copy of the TSR program from memory. Once the program has popped down, it will not be able to pop up again. If tsr_uninstall is called when the program is executed from DOS, it removes any previously loaded copy of itself.
- Return Value
-
0 Function successful 1 Can not load, program already loaded 2 Can not remove, the program is NOT loaded 3 Can not remove, another TSR program has been loaded on top of your program - Compatibility
- DOS Small memory model only.