From: mieze@forwiss.uni-passau.de (Thorsten Mieze)
Subject: Re: To all who want to program TSR
Organization: University of Passau, Germany
Date: Sun, 30 Aug 1992 11:24:58 GMT

Keywords: 

In <drice.714963151@ponder> drice@ponder.csci.unt.edu (Keith Rice)
writes :

>In <1992Aug27.153035.15061@tom.rz.uni-passau.de> 
>mieze@forwiss.uni-passau.de (Thorsten Mieze) writes:

>>Hi all!

>>During last time, there were often questions about programming TSR
>>in higher languages like C, PASCAL, MODULA etc. in this newsgroup.
>>Notice, that all TSR-programs need an include of assembler, which 
>>sets div. parameters (e.g. div. pointers of interrupts) to enable
>>programming the real TSR-program in higher language. The mostly 

>Not so.  In the book _Turbo Pascal 5.5_ from Borland/Osborne there
>is an example of a screen blanker (I think) TSR written entirely in
>TP 5.5.

That's right. Keith was so kind and sent me the program via email.
Since there are multiple users interested, I'll post it here. Please
understand this as an animation for your own programs, don't use it
commercially. Recognize part II of the article written after the 
example-progam.

{$M 2000,0,0}
{$R-,S-,I-,D+,F+,V-,B-,N-,L+ }
Program ScreenSaver;
Uses
  DOS, CRT;

Const
  TimerInt = $08;          (* Timer interrupt *)
  KbdInt   = $09;          (* Keyboard interrupt *)
  TimeLimit : Word = 5460; (* Wait 5 minutes before blanking *)

Var
  Regs     : Registers;

  Cnt      : Word; (* Counts timer ticks         *)
  PortNum  : Word; (* Port used to disable video *)
  PortOff  : Word; (* Value to disable video     *)
  PortOn   : Word; (* Value to enable video      *)

  OldKbdVec   : Pointer;
  OldTimerVec : Pointer;

  i    : Real;
  Code : Word;

(************************************************************)

Procedure STI;
InLine($FB);

(************************************************************)

Procedure CLI;
InLine($FA)

(************************************************************)

Procedure CallOldInt(Sub:Pointer);
Begin
InLine($9C/          { PUSHF                 }
       $FF/$5E/$06); { CALL DWORD PTR [BP+6] }
End;

(************************************************************)

Procedure Keyboard(Flags,CS,IP,AX,BX,CX,DX,
                   SI,DI,DS,ES,BP : Word); Interrupt;
Begin
CallOldInt(OldKbdVec);      (* Call original interrupt *)

If (Cnt >= TimeLimit) Then  (* Restore screen, if disabled *)
  Port[PortNum] := PortOn;
Cnt := 0;                   (* Reset counter *)
STI;
End;

(************************************************************)

Procedure Clock(Flags,CS,IP,AX,BX,CX,DX,
                   SI,DI,DS,ES,BP : Word); Interrupt;
Begin
CallOldInt(OldTimerVec);      (* Call original interrupt *)

If (Cnt > TimeLimit) Then     (* If timer limit is reached, *)
  Port[PortNum] := PortOff    (* disable video              *)
Else
  Inc(Cnt,1);
STI;
End;

(************************************************************)

Begin
(* If user entered a number parameter, *)
(* compute the delay factor.           *)

If ParamCount = 1 Then
  Begin
  Val(ParamStr(1),i,code);
  If (Code = 0) and (i > 0) and (i < 11) Then
    TimeLimit := Trunc(i*18.2*60);
  End;

Regs.AH := $0F;          (* Determine the type of video  *)
Intr($10,Regs);          (* adapter in use (Mono or CGA) *)

If Regs.AL = 7 Then      (* Mono adapter *)
  Begin
  PortNum  := $3B8;
  PortOff  := $21;
  PortOn   := $29;
  End
Else
  Begin
  PortNum  := $3D8;
  PortOff  := $25;
  PortOn   := $2D;
  End;

(* Save original interrupts *)
GetIntVec(KbdInt, OldKbdVec);
GetIntVec(TimerInt, OldTimerVec);

(* Install new interrupts *)
SetIntVec(TimerInt, @Clock);
SetIntVec(KbdInt, @Keyboard);

Cnt := 0;  (* Initialize counter *)
Keep(0);   (* Terminate and stay resident *)
End.

--------------------------------------------
So far, so good.This program includes little inlines, but we won't 
call them assemlerROUTINES... (not a flame) %)
Just one thing : This is a screen-saver, working absolutely 
correct. But this programm doesn't contain any DOS-commands/-inter-
rupts in its TSR part (Procedure CallOldInt, Procedure Keyboard and 
Procedure Clock). 
If you want to program TSR underlaying this example-program, be sure,
that you NEVER execute any DOS-interrupt or DOS-interrupt using com-
mand. A short example : It's easy, to execute the Interrupt 21h, func-
tion 30h, which will return the actual DOS-version...one of many ways
to execute a DOS-interrupt. Application :
         [...]
         var regs2 : registers;
         [...]
         regs2.ah := $30;
         intr ($21,regs2);
         [...]

Your stupid AT would starve in 99% at each execution. %( 
Motivation : the non-reentrance of DOS (that means, DOS can't store
             all relevant registers more than one time and will over-
             write these relevant informations, whenever an DOS-inter-
             rupt is executed, which interrupts the first access of 
             DOS). (hope it's clear %) )
=> Problem : How can I execute DOS-interrupts, since I need to use
             them.
Solution : 

Whenever DOS executes a DOS-function (e.g. interrupt) it sets a flag
called INDOS and resets it, when ready. The TSR routine which will use
DOS-routines, should read this flag, before going on. The INDOS flag
is installed to a fix place in the memory, and it's adress can be re-
ceived by excuting the DOS-interrupt 21h function 34h. If one sets a
pointer to this adress when installing the TSR routines, he can always
see, a DOS-routine is in execution, or not (flag >0 => DOS-routine 
execution). 
But unfortunately this isn't the whole solution. It'll be all fantas-
tic during another program is active, and not the DOS-Shell. The
INDOS-flag is reset mostly.
If there's no program running, the DOS-shell is active. But whenever
the DOS-shell is active, the INDOS-flag is set nearly the whole time, 
since DOS is waiting for input in a DOS-loop. The TSR-program can't 
run. But there's a way : while DOS is waiting for input, it uses the
interrupt 28h for activating bgrd-processes (such as printer-spooling)
each waiting-loop. At this time, it stores no relevant registers and
one can interrupt, although INDOS is set. If one writes his own 28h 
routine, in which it executes the original one, the TSR can be exe-
cuted afterwards. 
Now theres just one more problem : Whenever timecritical actions are
in execution, TSR may not interrupt. Timecritical actions are in gen-
eral access-routines to FD and HD. These routines use the interrupt 13h
during execution, so that one can write it's own 13h routine the same
way, one did with the 28h routine, and disable (flag) TSR execution
during 13h is active.

Now, the TSR should run without danger.

Thorsten Mieze 

(*   Katzen wuerden Whiskey saufen %)   *)


  
Newsgroups: alt.msdos.programmer,comp.os.msdos.programmer