logo To Foot
© J R Stockton, ≥ 2006-02-01

Borland Pascal Extensions.

No-Frame * Framed Index * Frame This
Links within this site :-

Doing Without the Crt Unit

See also Crt, about the Crt and WinCrt units.

The Crt unit, as delivered, is not altogether ideal - (a) it is one monolithic section, not splittable by smart linking; (b) on machines which are too fast for its (version-dependent) design, Delay can fail to operate correctly; (c) the initialisation section, in TP7/BP7, fails on machines of over about 200 MHz.

Patches (do not publish links to this directory) for Delay exist, and there are replacement units; but one may prefer not to use Crt if it can be avoided. The Pascal FAQ, item #124, on Crt.Delay RTE 200, includes a list of replacements for Crt routines, including Pedt Scragg's replacement Crt unit.

Retaining the Crt Unit

If the sole problem is that TP7 Crt initialisation fails RTE 200, and it is not actually necessary for Crt.Delay to be used (or just to be right), then it is worth considering the use of Turbo Pascal Version 5.0 to compile instead of TP7. Turbo and/or TPC can even be installed as a Tool in the TP7/BP7 IDE, so retaining the benefits of those IDEs. This does rule out, of course, TV, TD, DPMI, BREAK, CONTINUE, asm..end, and other novelties.

Using an ANSI driver

After Osmo Ronkanen : If your screen is set to respond to ANSI codes then you can have, for example :-

procedure GotoXY(X,Y : byte) ;
var m1, m2 : string [3] ;
begin str(X,m1) ; str(Y,m2) ; Write(#27'['+m2+';'+m1+'H') end ;

procedure ClrScr ;
begin Write(#27'[H'#27'[2J') end ; { JRS: <esc>[H not needed? }

procedure ClrEol ;
begin Write(#27'[K') end ;

Calling ROM/RAM BIOS or Ports Directly

Often, a routine in Crt is basically just a wrapper for a DOS BIOS call or port writes, such as may be done in TP6+ by an asm ... end block.

Clearing the Screen

One can use the ClrScr above to clear the screen.

An example, derived from Osmo Ronkanen ("This does not move the cursor. This is what ClrScr does."), is

 with Regs do begin
   ax := $600 ; bh := 7 ; cx := 0 ; dx := $184F end ;
   intr($10, regs) ;

Note, however, that the constant $184F presumes an 80×25 screen mode. It may be safe just to use an oversize area such as $FFFF. In the Crt unit, CX gets WindMin and DX gets WindMax.

The screen is cleared on setting the video mode, even if to the existing mode. After Manuel Algora <mac@encomix.es> (c.l.p.b., 11 Nov 1999) :-

procedure Cls ; assembler ;
asm  MOV AX,40h ; MOV ES,AX ;
  XOR AX,AX ; MOV AL,[ES:49h] ; INT 10h  end ;

procedure Cls ; assembler ;
asm  mov ah,0Fh ; int 10h ; mov ah,0 ; int 10h  end ;

Note that the MS-DOS CLS command does not zeroise the screen buffer; it sets all characters to $20=space and all attribute bytes to the current value. If the screen is cleared by zeroising, the cursor may flash black-on-black, and some applications may write in that manner too. See, therefore, "FillWord" in Borland Pascal Optimisation 2.

Consider for J := 0 to 99 do Writeln ;, noting that it leaves the cursor differently positioned; the method suits any language.

Saving the Screen

Don't presume that the screen will be in 80×25 colour mode and on Page 0. In a Windows DOS box, with a modern Crt display, 80×50 is very convenient (BP7 and TP5 work well with it); and miniature machines may have more limited screens. Relevant information is kept in Seg0040, and the means of using it to save a general text screen on the Heap (and to restore it) can be seen in my shower.pas by searching for the string "screen" and reading thereabouts. The program itself needs TP7; but the basic screen-handling code does not.

Speaker Control

procedure Silence ;
begin Port[$61] := Port[$61] and not $03 end {Silence} ;

procedure Speaker ;
begin Port[$61] := Port[$61] or $03 end {Speaker} ;

From a post by Klaus Hartnegg (not tested by me) :-

procedure sound(freq : word) ;
begin
  if freq = 0 then exit ;
  freq := 1193180 div freq ;      { JRS: 1193182 ! }
  port [$43] := $B6 ;             { set timer 2 to square wave generator }
  port [$42] := lo(freq) ;        { set reload value }
  port [$42] := hi(freq) ;
  port [$61] := port [$61] or 3 ; { enable speaker }
  end ;

ReadKey

In HUNT.PAS, there is a ReadChar, probably equivalent to Crt.ReadKey on the ordinary keys; all extended keys return #0 :-

function ReadChar : char { in lieu of Crt.ReadKey, for normal keys } ;
var R : registers ;
begin with R do begin AH := 0 ; Intr($16, R) ; ReadChar := char(AL) end ;
  end {ReadChar} ;

The following, for TP7/BP7, in LONGCALC.PAS, is more versatile, and extended keys can be distinguished :-

function GetKbd : word ; assembler ;
asm  mov AH,0 ; int 16h  end {GetKbd} ;

In KEYBUFF.PAS there is a ReadKey, equivalent to Crt.ReadKey on all keys - I hope :-

function ReadKey : char { in lieu of Crt.ReadKey } ;
const Buff : char = #0 ; var R : registers ;
begin
  if Buff<>#0 then begin ReadKey := Buff ; Buff := #0 end else
    with R do begin AH := 0 ; Intr($16, R) ;
    if AL=0 then Buff := char(AH) ;
    ReadKey := char(AL) end ;
  end {ReadKey} ;

In KEYBUFF, there is also a Wait(Tix) - see also Borland Pascal Wait and Delay.

Note - for reading keystrokes, see within TSFAQP #1.

KeyPressed

Derived from a post by Joe Merten <joe@jme.de>, JME Engineering Berlin :-
If your reason to replace the KeyPressed function is only that you don't want to use the Crt unit, try :-

function KeyPressed : boolean ; assembler ;
asm  mov AH,$01 ; int $16
  mov AL,TRUE ; JNZ @Ende ; mov AL,FALSE ; @Ende: end ;

But if you want to prevent the BIOS call, try :-

function KeyPressed : boolean ;
begin KeyPressed := MemW[Seg0040:$1A]<>MemW[Seg0040:$1C] end ;

which simply checks the emptiness of the keyboard-buffer.

Note that, in the case of a function key for which KeyPressed returns #0,#xxx, after just the #0 has been read KeyPressed=True but the buffer is empty.

Clearing the Keyboard Buffer

asm  mov ax,$0C06 ; mov dl,$FF ; int $21 end { may do it } ;

Output Redirection

One feature given by Crt is non-redirectable output, suitable for progress/failure messages :-

program P ; uses Crt ;
var F : text ;
begin
  Write('Not ') ;
  Assign(F, '') ; Rewrite(F) ; Writeln(F, 'Redirected') ; Close(F) ;
  end.

However, this may also be achieved without Crt by using the CON device :-

program P ;
var C, F : text ;
begin
  Assign(C, 'CON') ; Rewrite(C) ; Write(C, 'Not ') ; Close(C) ;
  Assign(F, '') ; Rewrite(F) ; Writeln(F, 'Redirected') ; Close(F) ;
  end.

Frank Heckenbach has shown how, in Pascal, to use StdErr by altering a Handle :-

uses Dos ;
var StdErr : Text ;
begin
  Assign(StdErr, '') ; Rewrite(StdErr) ; TextRec(StdErr).Handle := 2 ;
  Writeln(StdErr, 'Hello StdErr.') ;
  Close(StdErr) ;
  end.

But Michael Phillips wrote :-

var StdErr: Text;
  ...
  { open the device StdErr }
  Assign(StdErr, ''); { assign handle to StdOut }
  Rewrite(StdErr);
  TTextRec(StdErr).Handle := 2; { reset handle to StdErr }
  TTextRec(StdErr).BufSize := 1; { set up bufsize to 1 character }
  { Note: it is important to change the text file buffer size from the
    default of 128 characters. }

One may use an inline routine (Pedt Scragg) to write to Standard Error :-

procedure PrintString(S : string) ; FAR ;
begin
  inline(
    $1E/         {PUSH  DS}
    $C5/$76/$06/ {LDS   SI,[bp+6]} (* if not FAR, use bp+4 *)
    $FC/         {CLD}
    $AC/         {LODSB}
    $30/$E4/     {XOR   AH,AH}
    $91/         {XCHG  AX,CX}
    $B4/$40/     {MOV   AH,$40}
    $BB/$02/$00/ {MOV   BX,2} (* 2 = StdErr *)
    $89/$F2/     {MOV   DX,SI}
    $CD/$21/     {INT   $21}
    $1F)         {POP   DS} ;
 end {PrintString} ;

N.B. It seems that Roger Donais has a unit that will redirect output using variables of type text.

Additional

Bill Boulton has sent me more information (1998-11-02), with permission to copy :-


The subject of I/O redirection is fully explained in the TP manual in
the following sections.

1. Input & Output files -> The CRT Unit
2. Devices in Turbo Pascal -> The System Unit.

The standard
(i.e. most common) way of handling the files is as follows:

"stdin" or "input" is redirectable on handle 0 and is equivalent to:
        assign (input, ''); .........

"stdout" or "output" is redirectable on handle 1 and is equivalent to:
        assign (output, ''); ........

"stderr" on handle 2 is ALWAYS to the display and has no named
equivalent in TP. It is, however, equivalent to:
        assigncrt (output, ''); .......
which is precisely the way TP/BP initialises when using the CRT unit.

I have found it good practice to adopt the conventions of C programming
when writing filters and the like since the code is totally unambiguous.
Filter programs which give no visual feedback to the user are very
disconcerting, to say the least. BY using the STDERR file for a
heartbeat and error messages, the user can be greatly assured of correct
operation of the program.

CAVEAT: It seems inadvisable to combine keypressed/readkey with
redirected i/o in an application. The keyboard functions use the BIOS
while the redirected i/o is in the domain of DOS. I have experienced
unpredictable behaviour when combining these input forms and so now
avoid such a combination.

If a program expects redirected input and none has been established, it
will simply sit there doing nothing until the user hits ^Z <enter>
to cause it to terminate. Here is a filthy little inline function to
determine the source of input.

    { _ISTTY.ASM
    ; Return TRUE if handle is console device and FALSE if it is a file
    }
    function _istty (handle: word): boolean;
    inline (
        $5B/                { 0100    POP     BX           }
        $B4/$44/            { 0101    MOV     AH,44        }
        $B0/$00/            { 0103    MOV     AL,00        }
        $CD/$21/            { 0105    INT     21           }
        $B8/$01/$00/        { 0107    MOV     AX,0001      }
        $F6/$C2/$80/        { 010A    TEST    DL,80        }
        $75/$03/            { 010D    JNZ     0112         }
        $B8/$00/$00         { 010F    MOV     AX,0000      }
    );

To avoid this situation, simply call the function in the following
manner:

    if _istty (0) then begin
       writeln (stderr, 'Unable to process keyboard input');
       halt (1);
    end;

Bill Boulton <bill_boulton@winshop.com.au>

Non-Destructive Keyboard Read

To determine the next character in the keyboard buffer, without removing it from the buffer, see KEYBUFF.PAS.

Long File Names

In BP/TP, just avoid them.

But units to handle them can be found, such as (not tested by JRS) dos70p20.zip (197 Kbytes) "TP unit allows use of long filenames in TP" by Cristi Streng, in turbspec/. Try also Jason Burgon's newer site.

Also see Andreas Killer's site for LFN###.ZIP. 2000-02-19 : I'm now using LFN109 in program cheklinx (* LFN needs the {$T-} state *). 2000-06-03 : LFN110 is released. (AK's site not found, 2006-01-31) :-

65973 Jun 2 2000 ftp://garbo.uwasa.fi/pc/turbopa7/lfn110.zip
lfn110.zip TP6+ Support of long filenames under Win95/98, A.Killer

I am told that the Win32 limit on path length is 260 characters.

N.B. FindFirst(SR, '*.*', Anyfile) ; FindNext(SR) ; will show only part of how they're done. Scott Earnest : "LFN information is formatted in such a way that the attribute byte on an entry will be invalid, making DOS findfirst/findnext skip over the entry as invalid.". For full details, see Long File Names.

For use under NT4, The African Chief should help.

LFN problems

For an LFN caveat re XCOPY, and links, see Xcopy Xposed, in PCguide (Charles M. Kozierok); and see also the c.l.p.b mini-FAQ; and The Navas Group.

I have read that lfnbk.exe, on the installation disk, is useful.

Large Structures

In TP7/BP7, the maximum size of the stack segment, which holds non-global "normal" variables, is 64K (i.e. 65536 = $10000) bytes (the maximum size for a single variable is one less at $FFFF bytes); there is a 64K FAQ by Klaus Hartnegg.

The first limit can be circumvented by using Heap storage - look up New/Dispose and GetMem/FreeMem, and see how only a pointer is needed in the stack segment.

The second limit can often be circumvented by using an array of pointers to Heap storage; the array itself can be on the Heap. Consider :-

type
  A200 = array [1..200] of word ; P200 = ^A200 ;
  A100 = array [1..100] of P200 ; P100 = ^A100 ;
var A : A100 ; PA : P100 ;
  ...
  for J := 1 to 100 do New(A[J]) ;
  New(PA) ; for J := 1 to 100 do New(PA^[J]) ;
  ...
  { so you can now do such as: }
  A[100]^[200] := 100*200 ; PA^[100]^[200] := 100*200 ;

Array A uses 400 bytes of stack, pointer PA uses 4 bytes of stack; the rest of the data is in the Heap.

It may be necessary to review the settings of HeapLimit & HeapBlock, to economise on selectors.

Vast Data

Mark Iuzzolino <monsters@nmia.com> wrote (1997-04-05, c.l.p.b) :-

To be a little more specific, with Borland Pascal 7.0 - compiling for protected mode - it is possible to allocate up to 16 megabytes.. in 64K chunks. However, with an additional unit for BP 7.0, one can allocate all the memory on the PC (up to the limit of 2^46 bytes or 64 terabytes), and allocate it as one large contiguous chunk. If you have BP 7.0 and you are in need of large contiguous buffers, you should download how to do this.

Delphi, at least from Version 2, can handle variables bigger than 64K.

Array Bounds

The above implies that the difference between the bounds of an array dimension cannot reach 64K. There is, therefore, no benefit in allowing index types to be bigger than 16 bits, and it appears that indeed they cannot be (this not seen in manual or Help). Indeed, tests show that [99991..99997] and [longbool] are not acceptable.

Variable Length Arrays

There is no intrinsic provision for variable length arrays, but it is easy enough to work around it, as I have done in LONGCALC.PAS.

Some writers recommend such as :-

type Arr = array [1..1] of Thing (* <--- NO NO !!! *) ;
PArr = ^Arr ;
var PA : PArr ; Th : Thing ;
  ...
  GetMem(PA, NThing*SizeOf(Thing) ;
  PA^[Ix] := Th ;

As is, this is folly, since it only works if Range-Checking is off (whenever PA^[J] is accessed (except for J=1)). Declare const MaxBytes = $FFF0 ; (depending on version and mode, other values in $FFF0..$FFF8 may be OK; Delphi allows $40000000). The limit is written as a const expressed in Hex, to reduce typos. The first line should be such as :-

type Arr = array [1 .. MaxBytes div SizeOf(Thing)] of Thing ;
type Arr = array [0 .. Pred(MaxBytes div SizeOf(Thing)]) of Thing ;

Range-Checking can now be on throughout (though it will not protect against access past the end of the GetMem area [in DPMI mode, access outside the GetMem segment (which may be bigger than you think; see HeapLimit) is prevented by hardware]).

Re 2-D arrays? : on 17 Sep 1997 in comp.lang.pascal.borland, valderra@platon.ugr.es wrote :- "You can find an interesting solution about your problem in one of the math units by J. Deboerd (matrix.pas, I think)". And Jacques Guy wrote about his Dynamic arrays for TP5.5 and later. And Franz Glaser has FAQ information, "Dynamic arrays" and "TP-memory, Pointer Primer".

A Full Segment of 64K

Derived from a posting in c.l.p.b by Frank Heckenbach on 1997-02-19 :-

To allocate one full segment, i.e. exactly 65536 bytes, the following works (at least with TP 6.0, BP 7.0, real mode). It is also one of the shortest programs I know to use a linked list (whose only purpose is to take up memory with offset<>0), and the only use I know for a type that points to itself. Quite funny, after all...

{Copyright 1992, Frank Heckenbach}
TYPE
  P64K=^T64K;
  T64K=ARRAY[0..$FFFE] OF Byte;
               {^^^^^ should be $FFFF, of course, but that would
                generate an error (data structure too big). You
                can still use element [$FFFF] (with range checking
                off), since memory is allocated in 8 Byte blocks.}

FUNCTION Get64K:P64K;
TYPE TList=^TList;
VAR
  List,Temp:TList;
  p:P64K;
BEGIN
  List:=NIL;
  New(p);
  WHILE Ofs(p^)<>0 DO
    BEGIN
      Dispose(p);
      Temp:=List;
      New(List);
      List^:=Temp;
      New(p)
    END;
  WHILE List<>NIL DO
    BEGIN
      Temp:=List;
      List:=List^;
      Dispose(Temp)
    END;
  Get64K:=p
END;

Long Strings

To express a string constant which is too long to fit on a line, use '...'+'...', split at the "+"; the join occurs at compile time.

If you need to handle more than 255 characters at once, then you probably need to use C-style null-terminated strings; study the Strings unit and the PChar type.

32-Bit Code

Whole section needs checking

BP7 and TP7 generate 16-bit code, for 286 compatibility. Standard Longint access is as two words, therefore non-atomic; an intervening hardware interrupt may split access.

Within an asm ... end part, the assembler will not recognise 32-bit op-codes. However, one can use a db $66 ; prefix on many 16-bit op-codes to generate a 32-bit instruction. To avoid typos, make a mnemonic for it.

If a hardware interrupt routine uses any of EAX..EDX, the previous value must be restored.

If a hardware interrupt routine uses the FPU, it is not sufficient to save and restore the FPU registers, as the floating point code uses non-reentrant support routines. I have found it possible to use only 6-byte reals within the hardware routine, and only IEEE 4/8/10-byte ones outside, being careful to make any conversions atomic. See also Pascal/Delphi Rounding and Truncation.

Tools Menu

In BP7, the Tools menu can be enlarged by Options/Tools (IMHO, often not realised); it's a useful place to invoke one's favourite reformatter, and any earlier versions of TP with which one may need to check compatibility.

I'm told that it cannot run 32-bit protected mode programs such as TMT Pascal.

TPUs

Likewise for TPPs and TPWs.

Spawning

Simple, standard method (see online Help, and manuals):

{$M x, y, z : z must leave enough space}
  ...
  SwapVectors ;
  Exec(GetEnv('COMSPEC'), ' /C ' + CommandString) ;
  SwapVectors ;

The Command Processor can redirect or pipe output, so to give (for example) suppression of standard output, put ">nul" at the end of CommandString.

The above does incur the overhead of another command.com, and does not return the ErrorLevel of CommandString. If this is a problem, use

Exec('program', 'parameters') ;

See also TSFAQP #9, #75.

Mike wrote on 1998-02-25 :-

As I showed in a previous post, Ralf Brown's spawno works much better, swaps to EMS/XMS/DISK, and leaves only a 320-byte stub regardless of the size of the original program. His code has excellent error-checking, works with overlays, and uses only 4k. You can get it at:
ftp://garbo.uwasa.fi/pc/c-lang/spwno413.zip (100k)

The Environment

The Environment can be read with EnvCount, EnvStr and GetEnv, in the Dos Unit; and GetEnvVar in WinDos. The Environment is a copy of that of the parent. It can also be accessed, and altered, via PrefixSeg and the PSP, as a zero-terminated list of zero-terminated strings IIRC. The parent's PSP can also be located. Hence it is possible to alter an ancestral Environment; it is easy enough to change a character in it (see PATH-FIX.PAS), but it would be harder to extend it.

See also Timo Salmi's Batch FAQ and my MS-DOS Batch Files, MS-DOS Batch Programs, MS-DOS Batch Introduction.

Home Page
Mail: no HTML
© Dr J R Stockton, near London, UK.
All Rights Reserved.
These pages are tested mainly with Firefox 3.0 and W3's Tidy.
This site, http://www.merlyn.demon.co.uk/, is maintained by me.
Head.