

                                                                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!
