Page 1 Class TSR: an abstract base class for DOS resident programs. ------------------------------------------------------------ Author: John English (je@unix.brighton.ac.uk) Department of Computing University of Brighton Brighton BN2 4GJ, England. Copyright (c) J.English 1993. Permission is granted to use copy and distribute the information contained in this file provided that this copyright notice is retained intact and that any software or other document incorporating this file or parts thereof makes the source code for the TSR class of which this file is a part freely available. 1. Introduction. ---------------- This class provides a framework for writing memory-resident DOS programs (TSRs). TSRs produced using this class can be woken up by a specific key (the "hotkey") or after a specified number of timer ticks (the "timeslice") or a combination of both. Writing TSRs unaided is a non-trivial task, but this class provides the essential TSR functionality which allows you to concentrate on your application-specific requirements. TSRs written using this class will require a PC/AT compatible machine running DOS version 3 or higher. Compile them with Borland C++ version 3.0 or higher. To create a TSR using this class, you must derive a class for your application from it. You must supply a function "main" in your application class, which will be called whenever the TSR is woken up. To create a TSR, declare an instance of your derived class. A program built using an instance of your class can make itself resident using the member function "run", it can test if a copy is already loaded using the member function "loaded", and it can unload a previously loaded copy using the member function "unload". A foreground copy can also communicate with a resident copy using the functions "request" and "respond". Each TSR must be given a unique name up to 32 characters long which can be used to identify it once it is made resident. The constructor and "run" will set an internal status code if any errors are detected. If "run" returns, this will show the cause of the error. The status can be tested during program execution using the member function "status". This can be used to display meaningful error messages or to return as the program exit status. The member function "name" returns the name of the TSR, which can also be used in error messages. If you find this class useful or have any suggestions as to how it can be enhanced, please contact the author at one of the addresses given above. E-mail and postcards will both be welcome! 2. Deriving a new TSR class "MyTSR" from class TSR. --------------------------------------------------- The constructor for your derived class "MyTSR" must invoke the constructor for class TSR. The constructor for class TSR takes Page 2 two parameters: * a unique string which will be used to identify your TSR, and * the size in bytes of the stack to be used when the resident part of your TSR is active. If no stack size is specified, the default stack size is taken to be 1024 bytes. Class MyTSR must also provide a definition of a member function called "main" which will contain the application-specific code for your TSR. This will be executed whenever your TSR is woken up. It must be declared as follows: void main (int hotkey); Class MyTSR may also provide a destructor (~MyTSR), but this will only be called when the TSR is not made resident. Having created a derived class, you should then declare a single instance of this class in your program, as for example: MyTSR my_tsr; You must not declare more than one instance of a class derived from TSR in your program. Your program can obtain status information by calling the following member functions: my_tsr.name (); This returns the name of the TSR instance (as specified in the constructor call to class TSR). my_tsr.loaded (); This returns an integer result which will be non-zero if a copy of the TSR is already loaded. my_tsr.status (); This returns an integer result which will be non-zero if any errors have been detected by the constructor or by "run". The error codes are as follows: 1: incompatible DOS version (version 3 or higher required) 2: attempt to declare more than one instance of a TSR 3: unable to create stack of specified size 4: cannot allocate self-identify multiplex function 5: TSR already loaded 6: failed to make TSR resident (unlikely to occur) 7: user "startup" function reported failure my_tsr.unload (); This attempts to unload a previously-loaded copy of the TSR. It returns an integer result which will be non-zero if it failed, as follows: 1: TSR not loaded, so it cannot be unloaded. 2: Something else has hooked the same interrupts, so it cannot be unloaded. Page 3 3: Unable to free memory, so it cannot be unloaded (but it will now be disabled and will no longer respond to the hotkey or to timer ticks). This is unlikely to occur. 4: unable to free TSR environment space (although the TSR itself will have been successfully unloaded). This is unlikely to occur. 3. Making your TSR resident. ---------------------------- To make your TSR resident in memory, call the member function "run". "Run" requires two parameters: * A value representing the hotkey to be used to activate the TSR. This is described further below. * An optional timeslice size. This is an integer giving the number of timer ticks between TSR activations (a timer tick is approximately 55 milliseconds). If this parameter is omitted or zero, the TSR will only be activated when the hotkey is pressed. The hotkey should be a combination of values from the following list: Modifiers: TSR::ALT, TSR::CTRL, TSR::LSHIFT, TSR::RSHIFT Keycodes: TSR::KEY_A to TSR::KEY_Z, TSR::KEY_0 to TSR::KEY_9, TSR::ENTER, TSR::SPACE, TSR::F1 to TSR::F10 The hotkey value must not use more than one of the values from the "keycodes" list above. If you do not wish a hotkey to be used, specify a value of TSR::NONE for the hotkey parameter. Some examples of valid hotkey specifications are shown below: my_tsr.run (TSR::ALT + TSR::F1); // "my_tsr" should be woken up whenever Alt-F1 is pressed. my_tsr.run (TSR::LSHIFT + TSR::RSHIFT); // "my_tsr" should be woken up whenever the left and right // shift keys are pressed at the same time. If you specify a hotkey of TSR::NONE and no timeslice is specified either, "main" will never be woken up. You can use this to load interrupt handlers (using "startup" and "shutdown" as described below) which need to remain resident but do not need to interact with the user in any way. "Main" should be an empty function if this is the case, since it will never be called. If the TSR is installed successfully, "run" will not return. If "run" returns, it indicates that an error has occurred. The member function "status" (see above) can be used to determine the cause of the error. 4. Writing the member function "main". -------------------------------------- "MyTSR::main" (the main function of your derived class) will be called whenever the TSR is woken up, either as the result of the hotkey being pressed or the specified timeslice expiring. The Page 4 parameter "hotkey" will be non-zero if the TSR was woken up by the hotkey being pressed and zero if it was woken up because the timeslice expired. "Main" cannot perform operations which call DOS functions 00 - 0C (character I/O), 48 (allocate memory), 4C (terminate process) or 3E (close file, standard files only), but otherwise it is a normal C++ function. The following member functions can be used within "main": void pause (); This should be called whenever your "main" function is performing any lengthy processing. It allows other TSRs to execute while your TSR is active. void sync (); Timed activations normally occur every N timer ticks after "run" is called. This function resets the timer so that the next timed activation will happen N timer ticks from now, rather than when the current timer count expires. This can be useful to resynchronise timed activations if a hotkey is used to enable/disable TSR activity. int userbreak (); This returns a non-zero result if "control-break" has been pressed since it was last called. This can be polled from "main" if control-break detection is required. Your class may also overload the following functions to perform error recovery for the resident part of your program: critical_code critical_error (int n); Called when a critical error (Abort, Retry, Fail?) occurs during execution of "main". You must not call any DOS services other than functions 00 - 0C within this function. The result must be one of the values TSR::IGNORE, TSR::RETRY or TSR::FAIL. The default action for this function is to return TSR::FAIL. void dos_error (int fn, int ce, int cs, int ip); Called when an illegal DOS function is called from within "main" or "critical_error" (see above). The parameter "fn" is the function code from register AH; "ce" is non-zero if the error occurred while a critical error was being handled; "cs" and "ip" are the segment and offset of the return address from the offending interrupt. If this function is called it indicates a bug in your "main" or "critical_error" functions. You must not use any DOS services in this function (although BIOS services can still be used). The default action is to reset the screen to text mode if it is in graphics mode and then display an error message. These functions should not be called directly; they will be called automatically if an error occurs during execution of "main". 5. Initialisation and finalisation. ----------------------------------- Since a TSR which is made resident does not exit in the normal way, the destructor for your TSR will only be called if it is not made Page 5 resident. However, you may need to perform some initialisation when the TSR is made resident (e.g. hooking interrupts) and finalisation when it is unloaded (e.g. restore the original interrupt vectors). There are two virtual functions which can be overloaded to perform this sort of initialisation and finalisation: void startup (); Called by "run" when the TSR is being installed in memory. This can be used to provide application-specific initialisation (e.g. hooking interrupts). The default is to do nothing. void shutdown (); Called by "unload" when the TSR is being unloaded from memory. This can be used to provide application-specific finalisation (e.g. restoring hooked interrupt vectors). The default is to do nothing. These functions should not be called directly; they will be called automatically during TSR loading and unloading. 6. Communicating with a resident TSR. ------------------------------------- Sometimes it may be necessary to communicate with a resident copy of a TSR from a foreground program to adjust its parameters in some way. The functions "request" and "respond" provide a method to perform such communication. The program should provide an appropriate implementation for the virtual function "respond", which has the following specification: int respond (int fn, int far& p1, int far& p2); The parameter "fn" will be a function code in the range 0 to 127, and the parameters "p1" and "p2" can be used for an application-specific parameter list (which could be the segment and offset of a far pointer of a lengthier parameter list). A copy of the program loaded in the foreground can communicate with a previously-loaded resident copy by calling the function "request". "Request" requires three reference-to-integer parameters which will be used to call "respond" in the resident copy; the first one should contain the function code to be passed to "respond" and the remaining two will be passed to "respond" as the parameters "p1" and "p2". The result from "respond" will be stored in the first parameter, and the final values of "p1" and "p2" produced by "respond" will be stored in the last two. "Request" returns zero if the call is successful, and a non-zero result (TSR::NOT_LOADED) if there is no resident copy to communicate with. 7. A plea for feedback. ----------------------- If you use this class, please contact the author via the addresses at the beginning; if you don't have e-mail access please send me a postcard (I like postcards!) just to let me know you've looked at it. Feel free to suggest enhancements, find bugs or (better still) fix them and send me patches. Happy hacking!