logo To Foot
© J R Stockton, ≥ 2002-03-20

Borland Pascal Normal Usage.

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

Program Development

What precautions should be taken in program testing?

Among others, these :-

What tools are useful?

Command-Line Parameters

In TPW/BPW, there is a system variable CmdLine - type PChar?

For DOS / DPMI / Windows? modes, see the manuals or the on-line help for ParamCount, ParamStr. ParamStr(0) is special.

When a DOS program starts, the command line can be found as a standard string starting at offset $80 in the PSP - PString(Ptr(PrefixSeg, $80))^ or, better, ComStr(Ptr(PrefixSeg,$80)^). $80 holds the length byte; there is also I think a terminator (#13), after the string. The string can be edited in situ.

The following (using BP7, DOS mode) writes first the original set of parameters and last the UpCased set. The test parameter line was :-

abcdefgh 666 000 £££ AbC "x x"
program UPCMDLIN ;

procedure UpStr(var S : string) ;
var B : byte ;
begin Writeln(S) ;
  for B := 1 to Length(S) do S[B] := UpCase(S[B]) ;
  Writeln(S) end {UpStr} ;

var J : word ;

begin Writeln('UPCMDLIN.PAS') ;
  for J := 0 to ParamCount do Write(#32, ParamStr(J)) ; Writeln ;
  UpStr(ComStr(Ptr(PrefixSeg, $80)^)) ;
  for J := 0 to ParamCount do Write(#32, ParamStr(J)) ; Writeln ;
  Readln ;
  end.

Hence ParamStr evaluates, at the time of its call, the actual command line apparently supplied; which is no doubt why it is a function and not an array of strings. Note that the PSP string contains the original spacing.

Caveat

It is said that the PSP string may be overwritten by the system later on, though I've not noticed that myself, and others have been unable to find the effect. ParamStr(0) may be different, of course; but seems safer.

In an article in comp.lang.pascal.borland, Roger Donais (now <rdonais@leading.net>) replied :-

... but TP 2.0 was the last DOS version of TP that used FCB file i/o. The DOS version of TP 3.0 began using file handles and introduced the optional reset parameter that allowed specifying the byte size of an untyped file record. Prior to 3.0, all file i/o was done with FCB's through a 128 byte DTA, but as best as I can tell, even those do not alter the command line.

I ran a test program under TP 1.0, 2.0, and 3.0 for CP/M and TP 1.0, 2.0, 3.0, 4.0, 5.0, 5.5, 6.0, and 7.0 for DOS. The program did file i/o on text, typed, and untyped files. For versions that support them, I also checked FindFirst, FindNext, and FSearch. The command line remained unchanged under all versions of TP for both DOS and CP/M-80.

The only thing I can think of that prompts the warning about overwriting the command line is the conflict between the default FCBs and DTA that carried over from CP/M. The MS/DOS Encyclopedia comments on this are :-

For several reasons, the default FCBs and the DTA are often moved to another location within the program's memory area. First, the default DTA allows processing of only very small records. In addition, the default FCBs overlap substantially, and the first byte of the default DTA and the last byte of the first FCB conflict. Finally, unless either the command tail or the DTA is moved beforehand, the first FCB-related file or record operation will destroy the command tail.

I can see where a tech writer might use the above as basis for warning about the possibility of overwriting the command line. And if you are making your own system calls, or performing your own low-level file i/o, this might be a valid concern. But everything that I have tried seems to indicate that the command line is not affected by any standard TP statement.

On-Line Help

Before asking for help on any topic, remember to look in the printed manuals and, especially, the on-line Help. In BP/TP, Function key F1 gives context-dependent help, Shift-F1 gives the Help Index, Alt-F1 gives the previous Help, Ctrl-F1 gives the entry for the word under the cursor; BPW is similar.

IMHO, it is a deficiency of BPW Help that it does not recognise the BP-only keywords; it would be very useful if it were to show the entry for the best-corresponding BPW word, since it may well be the actual BPW word that has escaped one's mind.

Run-Time Checks

IMHO all possible run-time checks should be on all the time, except maybe in the case of stable innermost loops which have been shown to be range-safe. Also one might turn checks off for the final compilations of a deliverable program, if one prefers an undetected error to a halt.

In BP7, use "Alt-O" "C", then set "R" "S" "I" "c", and "D" "L" "y", and "T", to ON. In TP7, similarly, but there is no "y". Option "b" should normally be OFF; but a program depending on that should include {$B-}.

Also, perform your own checks where appropriate; if something is supposed to have been sorted, for example, it may be worth checking that it is.

In development, the time taken by the checks will be amply repaid, on every occasion that a check finds a fault - and in increased confidence otherwise.

When testing a real (MSDOS) mode program which uses the Heap, one can use protected (DPMI) mode to become eligible for Error 216.

When testing a protected mode program which uses the Heap, try

HeapLimit := 0 ;

which increases detection of storage errors (Runtime Error 216 becomes more sensitive).

Using Borland's Crt unit on a fast PC causes, at program startup, runtime error 200, which is fixable.

Does File 'X' Exist?

Beware of the question. In many cases it is not just a simple matter of "does the file exist", but "can it be read" or "can that name be written to" or "is the name in use". Many News articles give inadequate answers to tye question of whether a file exists.

For testing the existence and properties of a file/directory, GetFAttr seems to me to be better than {$I-}Reset{$I+} and simpler than FindFirst. See CHK_XIST, which uses GetFAttr; particularly its final comment. Nevertheless, subsequent new operations on the file should be in {$I-}, using IOResult, so that unexpected complications of a current operating system cannot lead to uncontrolled program failure.

However, GetFAttr treats the Volume-ID as a non-existent file; but this is consistent with the ability to create a file whose name matches the Volume-ID.

For finding the size, FindFirst is better than GetFSize (unless you are already using it as a file of type).

N.B. The "Reset" method uses a file handle; remember to Close the file.

Another method (the second parameter of FSearch is actually a list of directories) :-

function Exist(const Fn : string) : boolean ;
begin Exist := FSearch(Fn, '') <> '' end ;

CONST and VAR Declarations

In, at least, TP7..BP7,

CONST A = 3 ;
A is a "compile-time" constant and cannot change.
CONST B : byte = 5 ;
B is initialised, but **can** change during a run.
VAR C : byte ;
C is intended to vary, but *may* be initially zero.

Ideally, and in the spirit of CONST for procedure parameters as introduced in V7, "B" would not be allowed to change during a run, and

VAR D : byte = 6 ;
D is initialised, and changes during a run.

would be introduced; but ISTM that fixing "CONST B : type = value ;" would break far too many programs. However, the change is being gently introduced in Delphi.

That which follows the "=" can be an expression evaluated at compile time. Both any initialised const B and global var C will be stored in the Data Segment, which is limited to 64K bytes.

Procedure and Function Parameters

Let us consider the "var", "const", and "" keywords which precede the defining occurrences of procedure and function parameters, as before A, B, & C in

procedure P(var A : integer ; const B : string ; C : extended) ;

With "var", a pointer to the actual external parameter is passed, so that the actual parameter can be both read and written within the routine. The external parameter can be changed by the routine.

With "const", introduced in TP7/BP7, a pointer to the actual external parameter is again passed, but the compiler will not permit the generation of any code which might alter the value of the parameter. This is an efficient, safe way of handling a large, input-only parameter. The external parameter cannot be changed by the routine.

With "", the external parameter is evaluated where appropriate (it may be an expression or a function) or just copied, and this value is made available and alterable within the routine. The copy can be large (many bytes) if passing a string, array, or record. The external parameter cannot be changed by the routine.

Procedure and Function Identifiers as Parameters

The identifiers of procedures and functions are constants preset by the declaration; procedure and function types can be declared, as can variables of those types, and the identifiers can be passed as parameters.

Consider the working, tested BP7 program, which demonstrates a function User1 calling a function Real1 of type Fn :-

type
type1 = word ; type2 = integer ;
Fn = function (Dummy2 : type2) : type1 ;

function Real1(Real2 : type2) : type1 ; FAR ;
type Pt = ^type1 ;
begin Real1 := Pt(Addr(Real2))^ end ; { - silly example code }

function User1(Fun3 : Fn ; const Dummy3 : type2) : type1 ;
begin User1 := Fun3(Dummy3) end ; { - trivial example code }

begin
  Writeln(User1(Real1, 13):10, User1(Real1, -13):10) ;
end.

The most common mistake may be to omit the FAR. Otherwise the syntax and semantics seem as simple as one could wish, though maybe too simple to believe readily - and a mistake tends to lead to the obscurer regions of the error message set.

In TP5 you have to use {$F+} ... {$F-} instead of FAR (and const is not available).

Variable Number of Parameters

The compiler writer can implement procedures with a variable number of parameters, such as Read, Write, Inc, Dec; you cannot. You can work around some of the limitations of this, for instance by passing a list as a parameter.

Functions Returning Non-Simple Types

In Turbo- & Borland Pascal, a function can only return a simple type or a string; the inability to return structured types is regrettable (to the extent that, AIUI, it is available in Delphi).

Consider :-

type
  Ty = ... ; STy = string [SizeOf(Ty)] ;
  STr = record case byte of
    0 : (S : STy) ;
    1 : (L : byte ; T : Ty) end {ST} ;

function F1(X : STy) : STy ;
begin ...
  with STr(F) do begin L := SizeOf(Ty) ; T := STr(X).T end end {F1} ;

function F2(Xi : STy) : STy ;
var X : STr absolute Xi ;
begin ...
  with STr(F) do begin L := SizeOf(Ty) ; T := X.T end end {F2} ;

To word-align, change "L : byte" to "L, pad : byte" and make L & STy bigger.

See Pascal Maths for a function returning a Complex type.

Binary Bits

Counting bits in an ordinal : "J and Pred(J)" clears the lowest "1" bit, and so has one "1" bit fewer than "J" does, for J<>0; so :-

while J<>0 do begin
  Inc(Count) ; J := J and Pred(J) end ;

It seems that "J or Succ(J)" has one "1" bit more than "J" does, for Succ(J)<>0.

(x and -x) = x

may be a good power-of-two test - Verify!

For more such tricks, see at CalTech.

Printing and Other Output

It is a bad idea to build the name of a file or of a device into a program, if there is any possibility that it could need to be changed; make it selectable at run time, for flexibility in testing and operation. The Printer unit is easy to use, but not so flexible (it does have a special property, however, which may be necessary; see below).

In a DOS program, use of the default input and output permits redirecting or piping; if the CRT unit is in use, these can be regained by :-

Assign(F, '') ; Reset/Rewrite(F) ;

"I need to print data to a printer on LPT2: in a TP 7.0. The Lst option only prints to LPT1:. Any pointers?" Just use the method - see manual or Help - for printing to a file; but name the "file" 'LPT2'. Use procedure Flush as required. If using a laser printer, remember to print ^L = #12 = #$0C = FormFeed at the end, to flush the page.

Special file names used for devices include :-

NUL CON PRN LPT1 LPT2 LPT3 AUX COM1 COM2 COM3 COM4 CLOCK$ ;

others may be added by system drivers.

Ctrl-Z = ^Z = #26 = #$1A

Marius Gedminas <mgedmin@pub.osf.lt> wrote
Simple Assign and Reset/Rewrite with DOS devices don't always work as they are supposed to do. If you need to send special characters (such as #26) you have to change the device i/o mode to binary (also called raw; by default i/o mode is cooked - i.e. all special characters are interpreted). I had such problems when I tried to send a user-defined font to a printer when commands contained byte #26. Here is the solution :-
procedure SetDeviceRaw(var T) ; assembler ;
asm { Works with Text/typed/untyped files
      (as long as first word in TTextRec/TFileRec is DOS file handle) }
        LES     DI,T
        MOV     BX,WORD PTR ES:[DI]
        MOV     AX,4400H
        INT     21H
        TEST    DX,0080H
        JZ      @@1
        OR      DL,20H
        MOV     DH,0
        MOV     AX,4401H
        INT     21H
@@1:
end ;

Usage is simple :-

Assign(T, 'LPT2') ; Rewrite(T) ; SetDeviceRaw(T) ;
Write(T, ...)

I (JRS) have not tested this, but it seems familiar. Certainly ^Z = #26 is special under various circumstances.

To remove ^Z from within a text file, see REMOVE^Z.(PAS,EXE) in my programs directory.

Windows

A simple DOS program can often be made into a usable Windows program by adding

{$IFDEF WINDOWS} WinCrt, {$ENDIF}

to the "uses" statement, and taking a little care in the composition. No "printer" unit is provided for TPW/BPW; but one can write to a

var Lst : text ;

with

Assign(Lst, 'PRN') ; Rewrite(Lst) ;
Writeln(Lst, 'YYY') ;

as for a DOS program. Using the full printing capabilities of Windows takes substantially more work.

But I read that the printer drivers can be used, after writing a text file, with

procedure PrintFile(Filename : string) ;
begin
  SwapVectors ;
  Exec('c:\windows\notepad.exe', '/p ' + Filename) ;
  SwapVectors ;
  end ;

GDI Printers

GDI printers are now popular; they rely on the Windows Printing System. a GDI-only printer will not work under pure DOS - the PrintScreen key and "prompt>PRINT file" will not function - but may work in a Windows DOS box (untested).

PRNFLTR.PAS

In BP7 (TP7?), when the program file is printed, PRNFLTR.EXE may be used. If the resulting layout on your printer is not entirely to your taste, copy PRNFLTR.PAS to USERFLTR.PAS, and edit and compile that for use as the filter.

Printing the Text Screen

For programmed Print Screen, use

asm ; int 5 ; end {Osmo} ;

in BP7/TP7 ; or, in earlier TP, use

Intr(5, Regs) ;

(the content of Regs is irrelevant).

Reading Input

Files

To read a file which has its ReadOnly attribute bit set, use filemode := 0 ; as the default is to open in read/write mode.

Key Codes

Some characters, such as #, were found on the wrong keys of my UK keyboard; this was fixed by changing CodePage from 850 to 437 in autoexec.bat, and/or the same change in the Country command of config.sys. Check all DOS national settings, especially on a new Windows PC.

I have a small program, KEYCODES, to show what codes are generated on pressing a key.

Running DOS BP/TP in Windows

The non-Windows forms of BP and TP can run well in Windows 98 DOS boxes; but, as the products are older, their installation may need adjusting.

To keep the size of the DOS PATH down, I have a c:\utys directory on the path, and start such as BP by using c:\utys\bp.bat containing

D:\TANDON\BP7.01\BIN\BP %1 %2 %3 %4 %5 %6 %7 %8 %9

which brings up BP in an existing DOS box. This may save configuring a PIF file, or the equivalent.

(My BP.EXE reached my Win98 PC in a full INTERLNK/INTERSVR copy of the C: drive of my earlier Tandon 486/33 into D:\TANDON\.)

Having multiple DOS boxes is very useful, as is setting 80×50 mode if resolution permits.

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.