UNIT NewDelay; { Improved Delay routine For TP 6.0 and TP/BP 7.00/7.01 (real and protected mode). Version 1.00, released 29-09-97 (Note: The code itself has NOT changed since version 0.96! I just thought it was about time to finish beta stage and make a version 1.00) Please send any BUG REPORTS, COMMENTS and other FEEDBACK to me: frank(at)pascal.gnu.de or fjf(at)gmx.de Especially the accuracy probably still is to be improved a little. If someone wants to do that (using a high-precision timer - NOT the Bios system timer, or the GetTime procedure), please send me the results. The code is uncommented (as you will see yourself...), and I admit, some parts of it are probably not so easy to understand. If someone urgently needs some comments, I might write some. Copyright: Partially based on the Delay routine in the unit Crt, Copyright (C) 1988,91 Borland International. Any changes made are copyright 1996 by Frank Heckenbach; free for non-commercial. You may change the code below for your own use (e.g. remove the "IFDEF"ed parts you don't need), but don't give away modified versions of it. If you think you made an improvement, please contact me at the address shown above. You may NOT remove or modify these comments from the beginning up to "***"! Disclaimer: This code is provided "as is", and comes WITHOUT ANY WARRANTY, expressed or implied; without even the implied warranty of fitness for a particular purpose. I shall not be liable for any damages, whether direct, indirect, special, incidental or consequential arising from the use of the software or from the inability to use the software or from a failure of the software to operate in a manner desired by the user. In short: Use this code totally at your own risk! Features: * DelayCnt changed from Word to Longint to avoid overflow on a 200+ MHz CPU. * No init code necessary (unlike Crt) - at least for BP 7.0, if you don't use runtime patching (see below). Delay is initialized when first called. This saves about 1/18s and some bytes in programs that don't use Delay. Thus, the first delay will be a bit inaccurate (+/- 1/36.4 s). You can call Delay(0) at the start of the program to initialize Delay and avoid this inaccuracy. * Tries to avoid busy waiting by giving up timeslices via INT $2F,$1680 when running on a 80386 or above in a multitasking environment (as Windoze or Linux). E.g., in Linux, the CPU usage during a Delay dropped from about 90% with the old Delay code to under 0.1% with this replacement. In such an environment, of course, the delay may get inaccurate, especially longer than intended (with the old code as well as with this replacement). Credits to Walter Koch (walterk@ddorf.rhein-ruhr.de) for pointing me to this interrupt function. * Can patch Crt at runtime and prevent a runtime error caused by Crt's init code on a fast CPU. * Tested under NWDOS 7.0, Windoze 3.1 and the Linux DosEmu by the author. Dan Dumbrill <73170.1423@CompuServe.COM> says: "... on a PPro200 with Windows 95 ... the code seems to work fine with both protected and real modes." Michael Hermann says: "I tried the fix, and it works also under OS/2 and Win95." Further feedback about the behaviour under OS/2 and Win95 is wanted. History: 0.90 06-10-96 First release 0.91 20-10-96 Minor improvement in Delay 0.92 16-11-96 TP 6.0 compatibility added 0.93 18-11-96 Crt patching added 0.94 27-11-96 Added comments confirming OS/2 and Win95 compatibility 0.95 00-00-00 Skipped this version number! :-) 0.96 21-01-97 Added comments about using Make to modify Crt 1.00 29-09-97 Added comments about Linux (DosEmu) compatibility Officially ended beta stage Bug (possibly): Ralf Brown's interrupt list says about INT $2F,$1680: 'When called very often without intermediate screen output under WIN 3+, the VM will go into an idle-state and will not receive the next slice before 8 seconds. This time can be changed in SYSTEM.INI through "IdleVMWakeUpTime=". Setting to zero results in a long wait.' However, I could not see this effect, so my routine does nothing to prevent this problem. If you encounter this problem, please contact me (address: see above). *** Using this unit together with Crt Choose one of the three solutions described below: FIRST solution + Easiest - Requires changing all your units and programs - Does not fix the 200 MHz problem Use NewDelay AFTER Crt in the uses clause ("Uses ...,Crt,NewDelay,...") of the main program and of ALL units that use Crt, otherwise Crt's Delay routine will be used instead of the new one. On a 200+ MHz CPU, Crt's init code related to Delay will produce a runtime error. Using this unit in this way won't help against that. SECOND solution + "Clean" solution + No programs or other units have to be modified + Other units don't even have to be recompiled - only for BP 7.0 - Needs RTL source - Most work Modify Crt and rebuild your RTL. (Even if you are not afraid of the 200 MHz problem, it might be a good idea to change Crt, if you have the RTL source.) This is done as follows (Note: since Crt is copyrighted by Borland, I cannot distribute a modified version of it, nor will you be allowed to give away your modified version.): Preparations: * Read all of the following steps before you start. If there's anything you don't understand, don't start! * Of course you will make BACKUPS of any files you change during the following process (BEFORE you change anything)! * You must have BP7.0 with the RTL sources. If you only have TP 6.0 or TP 7.0, you can't use this solution. * Did I mention BACKUPS already? * You should have a bit more than basic experience working with BP, or have someone experienced to assist you in case of unexpected problems. * If you lose some important data without having made BACKUPS, you'll get some problems - so make BACKUPS NOW! Main part: * Remove all delay related parts from CRT.ASM (in the RTL\CRT directory). (Search for the string "delay" in the file, and keep your eyes open! Note: In the procedure "Initialize" it's the part from the line "MOV ES,Seg0040" to the line "MOV DelayCnt,AX", inclusively.) * Insert the implementation part of this unit - up to, but not including the line with "$IFDEF PATCHCRT" - into the implementation of CRT.PAS (same directory), and remove the line "procedure Delay; external;" from it. Don't change anything in the interface part of CRT.PAS. * Instead of the next two or three steps, you can change into the RTL directory and call "make \bp\rtl\bin\tpu\crt.tpu" and "make \bp\rtl\bin\tpu\crt.tpp" or simply "make", respectively. However, this may not work if your directories aren't set up exactly as the makefile expects them to. * Assemble CRT.ASM (with -d_DPMI_ to CRT.OBP for protected mode, and without this switch to CRT.OBJ for real mode) with TASM. * Compile CRT.PAS to CRT.TPU and CRT.TPP with BPC. * Update CRT.TPU and CRT.TPP in TURBO.TPL and TPP.TPL, respectively, with TPUMOVER (or, alternatively: remove them from TURBO/TPP.TPL and include the path to either CRT.PAS or CRT.TP? into your unit directories, and in the former case also the path to CRT.OB? into your object directories). * After modifying Crt this way, you don't have to use NewDelay in your programs, of course. THIRD solution + Easy + No RTL source needed + Only the program - no other units - has to be modified and recompiled - Kind of a workaround - Not for protected mode This method patches Crt at runtime, i.e. the code in the Crt unit is modified whenever a program compiled with this unit is started. However, Crt's Delay procedure is not really fixed, just "redirected" to this unit's Delay procedure. Therefore, two versions of Delay will exist in the executable file, making it bigger than actually necessary. Additionally, an interrupt handler is installed to trap the "division by zero" error caused by Crt's init code. This works only for real mode. However, Windoze does not have Crt at all (and WinCrt does not have Delay), and protected mode is only available with BP 7.0 which comes with the RTL source, so you can use the second solution in this case. It should be obvious that installing a (temporary) interrupt handler is also not a very "clean" solution, and makes the executable bigger than necessary, but anyway it works. How to do it: * Define the symbol PATCHCRT in this unit, i.e. remove the # in the following line: }{$DEFINE PATCHCRT}{ * Use NewDelay IMMEDIATELY BEFORE Crt, and before any units that use Crt in the uses clause of the main program ("Uses NewDelay,Crt...") * Insert the following line at the start of the main program: PatchCrt(Crt.Delay); } {$IFDEF WINDOWS} This unit is not for Windoze! {$ENDIF} INTERFACE {$IFDEF PATCHCRT} USES {$IFDEF WINDOWS}WinProcs{$ELSE}Dos{$ENDIF}; TYPE TCrtDelay=PROCEDURE(ms:Word); PROCEDURE PatchCrt(CrtDelay:TCrtDelay); {$ENDIF} {$IFDEF VER60} CONST Seg0040:Word=$40; Test8086:Byte=0; {Will be set to 2 if processor is 80386 or above} {$ENDIF} PROCEDURE Delay(ms:Word); IMPLEMENTATION CONST TimeSlice=100; {Threshold (in ms), above which Delay tries to give up time slices. Can be changed.} PROCEDURE DelayLoop; NEAR; ASSEMBLER; {Internal!} ASM @1:SUB AX,1 SBB DX,0 JC @2 CMP BL,ES:[DI] JE @1 @2: END; PROCEDURE Delay(ms:Word); ASSEMBLER; TYPE LongRec=RECORD Lo,Hi:Word END; CONST DelayCnt:Longint=0; {0 means unitialized} CONST op32=$66; {Prefix for 32bit operations} ASM MOV ES,Seg0040 MOV CX,ms MOV SI,$6C MOV AX,DelayCnt.LongRec.Lo OR AX,DelayCnt.LongRec.Hi JNE @2 { jump to delay loop if it's already initialized } MOV DI,SI MOV BL,ES:[DI] @1:CMP BL,ES:[DI] JE @1 { wait until bios time counter ticks } MOV BL,ES:[DI] MOV AX,-28 CWD CALL DelayLoop NOT AX NOT DX MOV BX,AX MOV AX,DX XOR DX,DX MOV CX,55 DIV CX MOV DelayCnt.LongRec.Hi,AX MOV AX,BX DIV CX MOV DelayCnt.LongRec.Lo,AX MOV CX,ms SUB CX,83 JBE @x @2:JCXZ @x XOR DI,DI MOV BL,ES:[DI] CMP Test8086,2 JNB @4 @3:XOR SI,SI @4:MOV BH,ES:[SI] @5:MOV AX,DelayCnt.LongRec.Lo MOV DX,DelayCnt.LongRec.Hi CALL DelayLoop CMP BH,ES:[SI] JNE @7 @6:LOOP @5 JMP @x @7:CMP CX,TimeSlice JB @6 DB op32;MOV DX,ES:[SI] @8:MOV AX,$1680 INT $2F OR AL,AL JNZ @3 DB op32;MOV AX,DX DB op32;MOV DX,ES:[SI] DB op32;SUB AX,DX JBE @9 DB op32;MOV AX,DX JMP @a @9:DB op32;NEG AX @a:DB op32;CMP AX,$4A7;DW 0 {CMP EAX,$10000 DIV 55} JA @x PUSH DX PUSH CX MOV CX,55 MUL CX POP CX POP DX SUB CX,AX JBE @x CMP CX,TimeSlice JNB @8 JMP @3 @x: END; {$IFDEF PATCHCRT} PROCEDURE Patch(OldProc,NewProc:Pointer); {General patch procedure. Patch writes a far jump to NewProc at the beginning of OldProc, thus directing all calls to OldProc to NewProc. OldProc and NewProc must both be pointers to far procedures/functions with the same number, order and type of parameters and the same return type (if functions). If they are different, no immediate error is generated, but most likely the program will crash when OldProc is called. Should also work with procedures/functions in overlaid units.} TYPE TFarJmp=RECORD OpCode:Byte; Operand:Pointer END; BEGIN {Get a writeable pointer to OldProc} {$IFDEF DPMI} OldProc:=Ptr(Seg(OldProc^)+SelectorInc,Ofs(OldProc^)); {$ENDIF} {$IFDEF WINDOWS} OldProc:=Ptr(AllocCStoDSAlias(Seg(OldProc^)),Ofs(OldProc^)); {$ENDIF} WITH TFarJmp(OldProc^) DO BEGIN OpCode:=$EA; {JMP FAR PTR} Operand:=NewProc END; {$IFDEF WINDOWS} FreeSelector(Seg(OldProc^)) {$ENDIF} END; {$IFDEF MSDOS} CONST OldInt0P:Pointer=NIL; VAR OldInt0:PROCEDURE(Flags:Word) ABSOLUTE OldInt0P; {"CONST OldInt0:PROCEDURE(Flags:Word)=NIL" is not possible in TP 6.0!} {$ENDIF} PROCEDURE PatchCrt(CrtDelay:TCrtDelay); BEGIN Patch(@CrtDelay,@Delay); {$IFDEF MSDOS} IF @OldInt0<>NIL THEN SetIntVec(0,@OldInt0) {No init bug has occurred!} {$ENDIF} END; {$IFDEF MSDOS} PROCEDURE NewInt0(Flags,CS,IP,AX,BX,CX,DX,SI,DI,DS,ES,BP:Word); INTERRUPT; BEGIN IF MemW[CS:IP]=$F1F7 {DIV CX} {Not a foolproof check, but should be sufficient, since NewDelay should be used IMMEDIATELY before Crt} THEN BEGIN { Writeln('Crt init bug trapped!'); } SetIntVec(0,@OldInt0); @OldInt0:=NIL; DX:=CX-1 END ELSE OldInt0(Flags) END; BEGIN GetIntVec(0,@OldInt0); SetIntVec(0,@NewInt0); {$ENDIF} {$ENDIF} {$IFDEF VER60} BEGIN ASM {Check for 80386} PUSHF POP AX OR AH,$F0 PUSH AX POPF PUSHF POP AX AND AH,$F0 JE @1 MOV Test8086,2 @1: END {$IFDEF PATCHCRT} {$IFDEF MSDOS} END {$ENDIF} {$ENDIF} {$ENDIF} Delay (1); { call once for initialization } END.