digitalmars.com                      
Last update Thu Jun 16 19:41:40 2011

tsr.h

This chapter describes: The tsr_functions allow you to write a program in Digital Mars C++ that can optionally reside in memory. Such a program is often called a Terminate-and-Stay Resident utility or simply TSR. In addition, your program can be given a slice of the processor's time, allowing a carefully written program to run as a background process.

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.h

This 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:

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_DEBUG
to 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 key
Meaning 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 key
Every 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 instead
In 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
Wordstar
Now 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
Then declare an int called HOTSHIFT and initialize it with your chosen hot shift, like this:
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+Q
Your 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:

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.

  1. Ensure that the debugging window does not open at any stage inside your TSR.
  2. Place displays in your program in order to identify the instruction or section of code that is causing problems.
  3. Check your own code thoroughly!
The tsr_functions have been tested extensively; no problems have been found. Make sure your code is sound before assuming the problem lies in the tsr_functions.

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.
Home | Compiler & Tools | IDDE Reference | STL | Search | Download | Forums