1493 lines
55 KiB
Plaintext
1493 lines
55 KiB
Plaintext
From ts@uwasa.fi Thu Mar 7 00:00:00 1996
|
||
Subject: FAQPAS3.TXT contents
|
||
|
||
Copyright (c) 1993-1996 by Timo Salmi
|
||
All rights reserved
|
||
|
||
FAQPAS3.TXT The third set of frequently (and not so frequently)
|
||
asked Turbo Pascal questions with Timo's answers. The items are in
|
||
no particular order.
|
||
|
||
You are free to quote brief passages from this file provided you
|
||
clearly indicate the source with a proper acknowledgment.
|
||
|
||
Comments and corrections are solicited. But if you wish to have
|
||
individual Turbo Pascal consultation, please post your questions to
|
||
a suitable Usenet newsgroup like news:comp.lang.pascal.borland. It
|
||
is much more efficient than asking me by email. I'd like to help,
|
||
but I am very pressed for time. I prefer to pick the questions I
|
||
answer from the Usenet news. Thus I can answer publicly at one go if
|
||
I happen to have an answer. Besides, newsgroups have a number of
|
||
readers who might know a better or an alternative answer. Don't be
|
||
discouraged, though, if you get a reply like this from me. I am
|
||
always glad to hear from fellow Turbo Pascal users.
|
||
|
||
....................................................................
|
||
Prof. Timo Salmi Co-moderator of news:comp.archives.msdos.announce
|
||
Moderating at ftp:// & http://garbo.uwasa.fi archives 193.166.120.5
|
||
Department of Accounting and Business Finance ; University of Vaasa
|
||
ts@uwasa.fi http://uwasa.fi/~ts BBS 961-3170972; FIN-65101, Finland
|
||
|
||
--------------------------------------------------------------------
|
||
51) I am running out of memory when compiling my large program.
|
||
52) How do I avoid scrolling in the last column of the last row?
|
||
53) How can one hide (or unhide) a directory using a TP program?
|
||
54) How do I test whether a file is already open in a TP program?
|
||
55) How can I test and convert a numerical string into a real?
|
||
56) How can I reverse a TP .EXE or .TPU back into source code?
|
||
57) How can I calculate the difference between two points of time?
|
||
58) Is a program running stand-alone or from within the IDE?
|
||
59) Please explain Turbo Pascal memory addressing to me.
|
||
60) How do I obtain a bit or bits from a byte, a word or a longint?
|
||
61) What are Binary Coded Decimals? How to convert them?
|
||
62) How can I copy a file in a Turbo Pascal program?
|
||
63) How can I use C code in my Turbo Pascal program?
|
||
64) How do I get started with the Turbo Profiler?
|
||
65) How can I detect if the shift/ctrl/alt etc key is pressed?
|
||
66) How do I get a base 10 logarithm in TP?
|
||
67) If Delay procedure does not work properly, how do I fix it?
|
||
68) How much memory will my TP program require?
|
||
69) How to detect if a drive is a CD-ROM drive?
|
||
70) How do I convert an array of characters into a string?
|
||
71) How do I get started with graphics programming?
|
||
72) Where to I find the different sorting source codes?
|
||
73) A beginner's how to write and compile units.
|
||
74) What are and how do I use pointers?
|
||
75) How can I read another program's errorlevel value in TP?
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:00:51 1996
|
||
Subject: Out of memory in compiling
|
||
|
||
51. *****
|
||
Q: I am running out of memory when compiling my large program. What
|
||
can I do?
|
||
|
||
A: If you are compiling your program from within the IDE (the
|
||
Integrated Development Environment) then select the Option from the
|
||
main menu, choose the Compiler item and set the Link buffer to
|
||
Disk. (Also make the Compile option Destination to be Disk).
|
||
If this is not sufficient, next resort to using the TPC command
|
||
line version of the Turbo Pascal compiler instead of the IDE. Use
|
||
the "Link buffer on disk" option.
|
||
Divide your program into units. It is advisable anyway for
|
||
modularity when your program size grows.
|
||
If you have extended memory, instead of TURBO.EXE use TPX.EXE, if
|
||
you have TP 7.0. If you are into protected mode programming then use
|
||
Borland Pascal BP 7.0.
|
||
|
||
A2: If you would prefer compiling your program from within the IDE
|
||
but cannot do it for the above reason (or if you would prefer to
|
||
compile your program from within your favorite editor instead of the
|
||
TP IDE) you can use the following trick. If your editor has a macro
|
||
language like most good editors do, the assign a hotkey macro that
|
||
compiles the current file with the TPC. If you are using SemWare's
|
||
QEdit editor you'll find such macros in ("Macros and configurations
|
||
for QEdit text-editor") ftp://garbo.uwasa.fi/pc/ts/tsqed17.zip and
|
||
in ftp://garbo.uwasa.fi/ts/tstse16.zip ("SAL macro sources to extend
|
||
The SemWare Editor").
|
||
Also your editor must be swapped to disk during the compilation
|
||
if memory is critical. There is a very good program for doing that:
|
||
ftp://garbo.uwasa.fi/pc/sysutil/shrom24b.zip ("Shell Room, Swap to
|
||
disk when shelling to application"). For example I invoke the QEdit
|
||
editor with using the following batch:
|
||
c:\tools\shroom -s r:\cmand -z 1024 c:\qedit\q %1 %2 %3 %4 %5 %6 %7
|
||
You'll find more about the switches in the Shell Room documentation.
|
||
The -s switch designates the swap destination (my r:\cmand directory
|
||
is on my ramdisk). The -z switch sets the shell environment size.
|
||
An unfortunate part is that the TP 5.0 Turbo Pascal IDE is about
|
||
the only program I know that is not amenable the to Shell Room
|
||
utility, so you cannot utilize Shell Room to swap the TP IDE to
|
||
disk. Blessfully, at least TP 7.0 no more has this problem.
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:00:52 1996
|
||
Subject: Last position write woes
|
||
|
||
52. *****
|
||
Q: How do I avoid scrolling in the last column of the last row?
|
||
|
||
A: If you use write or writeln at the last column of the last row
|
||
(usually 80,25) the screen will scroll. If you wish to avoid the
|
||
scrolling you'll have to use an alternative write that does not move
|
||
the cursor. Here is a procedure to write without moving the cursor
|
||
uses Dos;
|
||
procedure WriteChar (Character : char; fgColor, bgColor : byte);
|
||
var r : registers;
|
||
begin
|
||
FillChar (r, SizeOf(r), 0);
|
||
r.ah := $09;
|
||
r.al := ord(Character);
|
||
r.bl := (bgColor shl 4) or fgColor;
|
||
r.cx := 1; { Number of repeats }
|
||
Intr ($10, r);
|
||
end; (* writechar *)
|
||
Thus, if you wish to write to the last column of the last row, you
|
||
must first move the cursor to that position. That can be done in
|
||
alternative ways. One might get there by having written previously
|
||
on the screen (with writeln and write routines) until one is in that
|
||
position. Another alternative is using GoToXY(80,20), but then you
|
||
have to use the Crt unit. If you don't want to use it, then you can
|
||
move the cursor by employing "GOATXY As the ordinary GoToXY but no
|
||
Crt unit required" from ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip.
|
||
There is an alternative interrupt service ($0A) which does the
|
||
same as service $09, but uses the default colors instead. Just
|
||
substitute $0A for $09, and leave the r.bl assignment out of the
|
||
WriteChar routine.
|
||
Another option for writing anyhere on the screen without
|
||
affecting the cursor is using direct screen writes:
|
||
uses Dos;
|
||
procedure WriteChar (c : char; x, y : byte; fg, bg : byte);
|
||
var vidstart : word;
|
||
regs : registers;
|
||
begin
|
||
FillChar (regs, SizeOf(regs), 0);
|
||
regs.ah := $0F;
|
||
Intr ($10, regs); { Color or MonoChrome video adapter }
|
||
if regs.al = 7 then vidstart := $B000 else vidstart := $B800;
|
||
mem[vidstart:((y-1)*80+x-1)*2] := ord(c);
|
||
mem[vidstart:((y-1)*80+x-1)*2+1] := (bg shl 4) or fg;
|
||
end;
|
||
To write to the last position simply apply e.g.
|
||
WriteChar ('X', 80, 25, 14, 0); { Yellow on black }
|
||
The foreground (fg) and the background (bg) color codes are
|
||
Black = 0
|
||
Blue = 1
|
||
Green = 2
|
||
Cyan = 3
|
||
Red = 4
|
||
Magenta = 5
|
||
Brown = 6
|
||
LightGray = 7
|
||
DarkGray = 8
|
||
LightBlue = 9
|
||
LightGreen = 10
|
||
LightCyan = 11
|
||
LightRed = 12
|
||
LightMagenta = 13
|
||
Yellow = 14
|
||
White = 15
|
||
Blink = 128
|
||
Yet another option is the following, but it needs the Crt unit. On
|
||
the other hand, it uses the default color. This is quite a good and
|
||
easy solution. I captured this fairly frequent idea from a posting
|
||
by Robert Buergi (nbuero@hslrswi.hasler.ascom.ch).
|
||
uses Crt;
|
||
procedure WriteToCorner (c : char);
|
||
begin
|
||
Inc (WindMax);
|
||
GotoXY (80, 25);
|
||
write (c);
|
||
Dec (WindMax);
|
||
end; (* writeToCorner *)
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:00:53 1996
|
||
Subject: Hiding a directory
|
||
|
||
53. *****
|
||
Q: How can one hide (or unhide) a directory using a TP program?
|
||
|
||
A: Here is the code using interrupt programming. Incidentally,
|
||
since MS-DOS 5.0 the attrib command can be used to hide and unhide
|
||
directories.
|
||
(* Hide a directory. Before using it would be prudent to check
|
||
that the directory exists, and that it is a directory.
|
||
With a contribution from Jan Nielsen jak@hdc.hha.dk
|
||
Based on information from Duncan (1986), p. 410 *)
|
||
procedure HIDE (dirname : string);
|
||
var regs : registers;
|
||
begin
|
||
FillChar (regs, SizeOf(regs), 0); { standard precaution }
|
||
dirname := dirname + #0; { requires ASCIIZ strings }
|
||
regs.ah := $43; { function }
|
||
regs.al := $01; { subfunction }
|
||
regs.ds := Seg(dirname[1]); { point to the name }
|
||
regs.dx := Ofs(dirname[1]);
|
||
regs.cx := 2; { set bit 1 on } { to unhide set regs.cx := 0 }
|
||
Intr ($21, regs); { call the interrupt }
|
||
if regs.Flags and FCarry <> 0 then { were we successful }
|
||
writeln ('Failed to hide');
|
||
end; (* hide *)
|
||
|
||
A2: An alternative method by Dr. Abimbola Olowofoyeku
|
||
laa12@seq1.keele.ac.uk. No paths.
|
||
procedure HIDE (dirname : string);
|
||
var FileInfo : searchRec;
|
||
f : file;
|
||
begin
|
||
FindFirst (dirname, Directory, FileInfo);
|
||
while DosError = 0 do
|
||
begin
|
||
assign (f, FileInfo.Name);
|
||
SetFAttr (f, Hidden);
|
||
FindNext (FileInfo);
|
||
end;
|
||
end; (* hide *)
|
||
{}
|
||
procedure UNHIDE (dirname : string);
|
||
var FileInfo : searchRec;
|
||
f : file;
|
||
begin
|
||
FindFirst (dirname, AnyFile, FileInfo);
|
||
while DosError = 0 do
|
||
begin
|
||
assign (f, FileInfo.Name);
|
||
SetFAttr (f, Archive);
|
||
FindNext (FileInfo);
|
||
end;
|
||
end; (* unhide *)
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:00:54 1996
|
||
Subject: Testing file opened status
|
||
|
||
54. *****
|
||
Q: How do I test whether a file is already open in a TP program?
|
||
|
||
A: This question is best answered by providing the code:
|
||
uses Dos;
|
||
{... for non-text files ...}
|
||
function ISFOPEN (var filePointer : file) : boolean;
|
||
begin
|
||
isfopen := FileRec(filePointer).mode <> FmClosed;
|
||
end;
|
||
{}
|
||
{... for text files ...}
|
||
function ISTOPEN (var filePointer : text) : boolean;
|
||
begin
|
||
istopen := TextRec(filePointer).mode <> FmClosed;
|
||
end;
|
||
{}
|
||
procedure TEST; { Testing a non-text file }
|
||
const name = 'R:\TMP';
|
||
var f : file;
|
||
begin
|
||
Assign (f, name);
|
||
writeln ('File ', name, ' is open is ', ISFOPEN(f));
|
||
{$I-} rewrite (f); {$I+}
|
||
if IOResult <> 0 then
|
||
begin
|
||
writeln ('Failed to open ', name);
|
||
exit;
|
||
end;
|
||
writeln ('File ', name, ' is open is ', ISFOPEN(f));
|
||
close(f);
|
||
writeln ('File ', name, ' is open is ', ISFOPEN(f));
|
||
end;
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:00:55 1996
|
||
Subject: From string to real
|
||
|
||
55. *****
|
||
Q: How can I test and convert a numerical string into a real?
|
||
|
||
A1: An easy task in Turbo Pascal but in standard Pascal this
|
||
frequent task is much trickier. Here are both the Turbo Pascal and
|
||
Standard Pascal versions for general edification :-).
|
||
(* Convert and test a numerical string with Turbo Pascal *)
|
||
function DIGVALFN (mj : string; var ok : boolean) : real;
|
||
var k : integer;
|
||
x : real;
|
||
begin
|
||
Val (mj, x, k);
|
||
ok := k = 0;
|
||
if ok then digvalfn := x else digvalfn := 0;
|
||
end; (* digvalfn *)
|
||
{}
|
||
(* Convert and test a numerical string with standard Pascal routines only *)
|
||
procedure DIGVAL (mj : string; var number : real; var ok : boolean);
|
||
label 1;
|
||
var il, lenl, pl, kl1, kl2 : integer;
|
||
nrol : boolean;
|
||
numberdl : real;
|
||
begin
|
||
ok := true; lenl := Length (mj); nrol := false; pl := 0; number := 0.0;
|
||
if lenl = 0 then ok := false;
|
||
for il:=2 to lenl do if (mj[il]='-') or (mj[il]='+') then ok := false;
|
||
for il:=1 to lenl do
|
||
case mj[il] of
|
||
'0'..'9','+','-','.' : ; else ok := false;
|
||
end;
|
||
for il:=1 to lenl do
|
||
case mj[il] of '0'..'9' : begin nrol := true; goto 1; end; end;
|
||
1: if nrol = false then ok := false;
|
||
for il:=1 to lenl do if mj[il] = '.' then pl := pl + 1;
|
||
if pl > 1 then ok := false;
|
||
kl1:=1; kl2:=lenl+1; if (mj[1]='-') or (mj[1]='+') then kl1 := 2;
|
||
for il:=1 to lenl do if mj[il] = '.' then kl2 := il;
|
||
if kl2-kl1 > 38 then ok := false;
|
||
if ok then
|
||
begin
|
||
number:=0; numberdl:=0;
|
||
for il:=kl1 to kl2-1 do number := (ord(mj[il])-48)+10*number;
|
||
if kl2 < lenl+1 then
|
||
for il:=lenl downto kl2+1 do
|
||
numberdl := (ord(mj[il])-48)/10+numberdl/10;
|
||
number := number + numberdl;
|
||
if mj[1]='-' then number := -number;
|
||
end; {if ok}
|
||
end; (* digval *)
|
||
{}
|
||
procedure TEST;
|
||
var s : string; r : real; ok : boolean;
|
||
begin
|
||
s := '123.41';
|
||
r := DIGVALFN (s, ok);
|
||
if ok then writeln (r) else writeln ('Error in ', s);
|
||
DIGVAL (s, r, ok);
|
||
if ok then writeln (r) else writeln ('Error in ', s);
|
||
end;
|
||
|
||
A2: The conversion can be in the other directorion as well. Here is
|
||
how to convert an integer into a string with standard Pascal
|
||
routines only.
|
||
function CHRIVLFN (number : integer) : string;
|
||
var il, pl, al : integer;
|
||
cl, mj : string;
|
||
isNeg : boolean;
|
||
begin
|
||
if number < 0 then begin
|
||
isNeg := true; number := -number; end
|
||
else isNeg := false;
|
||
pl := 0; mj := ''; cl := '';
|
||
repeat
|
||
pl := pl + 1;
|
||
al := number mod 10;
|
||
cl := cl + chr(al+48);
|
||
number := number div 10;
|
||
until number = 0;
|
||
if isNeg then begin pl := pl + 1; cl[pl] := '-'; end;
|
||
for il := 1 to pl do mj := mj + cl[pl+1-il];
|
||
chrivlfn := mj;
|
||
end; (* chrivlfn *)
|
||
{}
|
||
procedure TEST;
|
||
var s : string; j : integer;
|
||
begin
|
||
j := 12341;
|
||
s := CHRIVLFN (j);
|
||
writeln (s);
|
||
end;
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:00:56 1996
|
||
Subject: Decompiling a TP .EXE
|
||
|
||
56. *****
|
||
Q: How can I reverse a TP .EXE or .TPU back into source code?
|
||
|
||
A: This is simply asking too much. You cannot decompile a TP
|
||
program in a manner that would give you back the original source.
|
||
This method of reverse engineering is not on in actual practice.
|
||
Quoting Jeroen Pluimers (jeroenp@dragons.nest.nl) "During the
|
||
compilation, important information get's lost about variables,
|
||
types, identifiers etc. Writing a Pascal Decompiler is impossible.
|
||
The best you can achieve is a disassembler that can help you
|
||
recognize some Pascal statements."
|
||
You might note that this question somewhat resembles another
|
||
frequent question "How can I convert a TPU unit of one TP version to
|
||
another?" which cannot be solved without the original source code.
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:00:57 1996
|
||
Subject: Calculating date/time differences
|
||
|
||
57. *****
|
||
Q: How can I calculate the difference between two points of time?
|
||
|
||
A: This is an unconfirmed answer so be a little careful with it.
|
||
But at the very least it shows some interesting information about
|
||
Turbo Pascal date/time conventions and how to declare and initialize
|
||
typed constants if they are records.
|
||
program TimDifTest;
|
||
uses Dos;
|
||
const a : DateTime
|
||
= (year:1992; month:10; day:24; hour:5; min:29; sec:38);
|
||
b : DateTime
|
||
= (year:1993; month:11; day:23; hour:6; min:30; sec:51);
|
||
var aLong, bLong, cLong : longint;
|
||
c : DateTime;
|
||
begin
|
||
PackTime (a, aLong);
|
||
PackTime (b, bLong);
|
||
cLong := bLong - aLong;
|
||
UnpackTime (cLong, c);
|
||
writeln (c.year-1980, ' ', c.month, ' ', c.day, ' ',
|
||
c.hour, ' ', c.min, ' ', c.sec);
|
||
end.
|
||
More generally than for dates between 1980 and 2079, or for more
|
||
accurate results, the difference between two date/times can be
|
||
calculated using Zeller's congruence (see the item "I want code that
|
||
gives the weekday of the given date"). First calculate Zeller's for
|
||
both the dates, convert them, and the hour, min, and sec into
|
||
seconds, subtract, and convert back.
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:00:58 1996
|
||
Subject: Stand-alone or from IDE
|
||
|
||
58. *****
|
||
Q: Is a program running stand-alone or from within the IDE?
|
||
|
||
A: Not all questions have an answer yet. I posed this question to
|
||
the the late UseNet newsgroup comp.lang.pascal, but we have not
|
||
found an answer that would be general for all MS-DOS versions. The
|
||
closest we have comes from dmurdoch@mast.queensu.ca Duncan Murdoch
|
||
(naturally :-). I have done some slight editing of Duncan's
|
||
solution.
|
||
uses Dos;
|
||
type Pchar = ^Char;
|
||
function Asciiz2Str (p : Pchar) : string;
|
||
var
|
||
result : string;
|
||
len : byte;
|
||
begin
|
||
len := 0;
|
||
while (p^ <> #0) and (len < 255) do
|
||
begin
|
||
inc(len);
|
||
result[len] := p^;
|
||
inc(longint(p));
|
||
end;
|
||
result[0] := chr(len);
|
||
Asciiz2Str := result;
|
||
end;
|
||
{}
|
||
var parentSeg : ^word;
|
||
p : pchar;
|
||
begin
|
||
if swap(DosVersion) < $0400 then
|
||
writeln ('Requires Dos 4.0+')
|
||
else begin
|
||
parentSeg := ptr (prefixSeg, $16);
|
||
p := ptr (ParentSeg^-1, 8);
|
||
writeln ('I was launched by ', Asciiz2Str(p));
|
||
end;
|
||
end.
|
||
Another suggestion has been that the contents of ParamStr(0) would
|
||
show the launching program. I tested this on several configurations
|
||
and TP versions and found no evidence that the contention would
|
||
hold.
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:00:59 1996
|
||
Subject: Memory Addressing
|
||
|
||
59. *****
|
||
Q: Please explain Turbo Pascal memory addressing to me.
|
||
|
||
A: This is far from an easy question, but let's see what we can do.
|
||
The origins of the difficulties are in the design of the 8086 chip
|
||
which still restricts all Turbo Pascal applications (which contrary
|
||
to Borland Pascal use the original real mode). The 8086 (aka real
|
||
mode) addressing is based on 16-bit registers. As you probably know
|
||
2^16 is 65536 which means that you cannot directly point to all
|
||
addresses of the lower and upper memory, which ranges from 0 to
|
||
1048575 (2^20-1). Thus all the memory addresses are pointed to into
|
||
two parts in TP programs, the segment and the offset. The following
|
||
example of the PC's memory illustrates.
|
||
|
||
Decimal Hexa-
|
||
address decimal Segment Offset What
|
||
0 $00000 $0000 $0000 Conventional memory starts
|
||
1043 $00413 $0040 $0013 Base memory in Kb, a word
|
||
655359 $9FFFF $9000 $FFFF Conventional memory ends
|
||
655360 $A0000 $A000 $0000 Upper memory begins
|
||
1048575 $FFFFF $F000 $FFFF Upper memory ends
|
||
|
||
To exemplify, let's look at some alternative ways we could access
|
||
the information about the amount of the base memory. It is very
|
||
straight-forward, since in a PC that information is at the fixed
|
||
memory location show by the above table. We know this in advance.
|
||
Using direct memory accessing we could write
|
||
var memsize : word;
|
||
memsize := MemW [$0040:$0013];
|
||
writeln (memsize);
|
||
{.. or ..}
|
||
var memsize : word absolute $0040:$0013;
|
||
writeln (memsize);
|
||
If you are not familiar with the true meaning of pointers, they may
|
||
feel confusing, but what they basically are is just what the name
|
||
indicates, pointers to memory locations. Study the following
|
||
example.
|
||
var memsizePtr : ^word; { A pointer to a word }
|
||
begin
|
||
memsizePtr := ptr ($40, $13); { Assign the pointer a value }
|
||
writeln (memsizePtr^); { Write what is in the address }
|
||
end. { that was pointed to }
|
||
This was relatively simple, since we knew in advance the location of
|
||
the information. Lets look at a case where we do not know that.
|
||
Consider
|
||
var x : word;
|
||
begin
|
||
x := 1223;
|
||
writeln (x);
|
||
end.
|
||
We have a variable x somewhere in the memory, and we can refer to it
|
||
without ever needing to know where the variable actually is in the
|
||
memory. But how does one find out if one for some reason wants or
|
||
needs to know?
|
||
var x : word;
|
||
Segment : word;
|
||
Offset : word;
|
||
y : ^word;
|
||
begin
|
||
x := 1223;
|
||
writeln (x);
|
||
Segment := Seg(x);
|
||
Offset := Ofs(x);
|
||
writeln ('Variable x is at $', HEXFN(Segment), ':$', HEXFN(Offset));
|
||
{... one test to ensure that the value really is in there ...}
|
||
writeln (MemW [Segment:Offset]);
|
||
{... another test to demonstrate that the value really is in there ...}
|
||
y := Addr(x);
|
||
writeln (y^);
|
||
end.
|
||
Next consider
|
||
var xPtr : ^word;
|
||
Segment : word;
|
||
Offset : word;
|
||
yPtr : ^word;
|
||
begin
|
||
xPtr^ := 1223;
|
||
writeln (xPtr^);
|
||
Segment := Seg(xPtr^);
|
||
Offset := Ofs(xPtr^);
|
||
writeln ('$', HEXFN(Segment), ':$', HEXFN(Offset));
|
||
{... a test to ensure that the value really is in there ...}
|
||
yPtr := Ptr (Segment, Offset);
|
||
writeln (yPtr^);
|
||
end.
|
||
A further aspect of pointers is that you can utilize them to put a
|
||
variables onto the heap instead of the data segment so that you
|
||
won't run so easily out of space.
|
||
var xPtr : ^word;
|
||
begin
|
||
{ Put it onto the heap }
|
||
New (xPtr);
|
||
xPtr^ := 1223;
|
||
writeln (xPtr^);
|
||
{ Get rid of it }
|
||
Dispose (xPtr); xPtr := nil;
|
||
readln;
|
||
end.
|
||
Let us return to the addressing. The formulas for converting between
|
||
the addresses (sent in by Duncan Murdoch) are
|
||
Physical := longint(segment)*16 + offset;
|
||
{}
|
||
Segment := Physical div 16;
|
||
Offset := Physical mod 16; { This gives the normalized form }
|
||
There are multiple Segment:Offset pairs that refer to the same
|
||
address, e.g. $0000:$0413 and $0040:$0013. The normalized addresses,
|
||
with the offset in the range of 0 to $F, are, however, unique. An
|
||
example $0041:$0003.
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:00:60 1996
|
||
Subject: Getting a bit from a byte
|
||
|
||
60. *****
|
||
Q: How do I obtain a bit or bits from a byte, a word or a longint?
|
||
|
||
A: For bit operations think of the variable as a binary number
|
||
instead of a decimal. Consider for example
|
||
var x : word;
|
||
x := 219;
|
||
In binary presentation it is
|
||
The word 0000 0000 1101 1011
|
||
Position in the word FEDC BA98 7654 3210
|
||
|
||
Say you need the value of bit 6 (the seventh bit) in the word. You
|
||
can "and" the following words
|
||
0000 0000 1101 1011 (219)
|
||
0000 0000 0100 0000 ( 64)
|
||
In decimal TP notation this amounts to
|
||
var b : word;
|
||
b := x and 64;
|
||
The value of b is now
|
||
0000 0000 0100 0000 ( 64)
|
||
To get the bit value (0 or 1) you need to shift the result right by
|
||
six steps, that this the expression becomes the often seen but
|
||
cryptic
|
||
b := (x and 64) shr 6;
|
||
which means that the value of b is finally 1 in this example.
|
||
|
||
Ok, but what then if you need the combined value of bits six and
|
||
seven. The answer is evident if you consider the binary presentation
|
||
0000 0000 1101 1011 (219)
|
||
0000 0000 1100 0000 (192)
|
||
hence
|
||
b := (x and 192) shr 6;
|
||
which will give 3 as it should.
|
||
|
||
So far, so good. What if you need to turn on bit nine in a word
|
||
without interfering with the other bits. The binary presentation,
|
||
again, is the key. You'll have to "or" the following
|
||
0000 0000 1101 1011 (219)
|
||
0000 0010 0000 0000 (512)
|
||
that is
|
||
x := x or 512;
|
||
This results to
|
||
0000 0010 1101 1011 (731)
|
||
|
||
What if you wish to turn off, say bit 6, in
|
||
0000 0000 1101 1011 (219)
|
||
1111 1111 1011 1111 (65471)
|
||
This is achieved by
|
||
x := 219;
|
||
x := x and 65471;
|
||
This results to
|
||
0000 0000 1001 1011 (155)
|
||
|
||
Consider the following application as an example. The number of a
|
||
PC's floppy disk drives (minus one) is stored in bits 6 and 7 in a
|
||
word returned by interrupt $11. This is the code to find out how
|
||
many disk drives a PC has.
|
||
uses Dos;
|
||
function NrOfFDiskDrives : byte;
|
||
var regs : registers;
|
||
begin
|
||
Intr ($11, regs);
|
||
NrOfFDiskDrives := ((regs.ax and 192) shr 6) + 1;
|
||
end;
|
||
|
||
A tip from Duncan Murdoch. You might wish to predefine the
|
||
following constants for easier handling
|
||
const bit0 = 1;
|
||
bit1 = 2;
|
||
bit2 = 4;
|
||
:
|
||
bit15 = 32768;
|
||
:
|
||
bit31 = 2147483648;
|
||
Or to put it slightly differently as Dr John Stockton
|
||
jrs@dclf.npl.co.uk suggests
|
||
const
|
||
bit00=$00000001; bit01=$00000002; bit02=$00000004; bit03=$00000008;
|
||
bit04=$00000010; bit05=$00000020; bit06=$00000040; bit07=$00000080;
|
||
:
|
||
bit28=$10000000; bit29=$20000000; bit30=$40000000; bit31=$80000000;
|
||
Finally, you also might want to look at the item "Getting a nybble
|
||
from a byte".
|
||
--------------------------------------------------------------------
|
||
From ts@uwasa.fi Thu Mar 7 00:01:01 1996
|
||
Subject: Binary Coded Decimals
|
||
|
||
61. *****
|
||
Q: What are Binary Coded Decimals? How to convert them?
|
||
|
||
A: Let us look at full integers only and skip the even more
|
||
difficult question of BCD reals and BCD operations.
|
||
Decimal Hexa BCD
|
||
1 $1 1
|
||
: $9 9
|
||
10 $A ..
|
||
: : :
|
||
12 $C ..
|
||
: : :
|
||
16 $10 10
|
||
17 $11 11
|
||
18 $12 12
|
||
: : :
|
||
Consider the last value, that is BCD presentation of 12. The
|
||
corresponding hexadecimal is $12 (not $C as in normal decimal to
|
||
hexadecimal conversion). The crucial question is how to convert
|
||
12BCD to $12 (or its normal decimal equivalent 18). Here is my
|
||
sample code:
|
||
type BCDType = array [0..7] of char;
|
||
{}
|
||
procedure StrToBCD (s : string; var b : BCDType);
|
||
var i, p : byte;
|
||
begin
|
||
FillChar(b, SizeOf(b), '0');
|
||
p := Length (s);
|
||
if p > 8 then exit;
|
||
for i := p downto 1 do b[p-i] := s[i];
|
||
end; (* strtobcd *)
|
||
{}
|
||
function BCDtoDec (b : BCDType; var ok : boolean) : longint;
|
||
const Digit : array [0..9] of char = '0123456789';
|
||
var i, k : byte;
|
||
y, d : longint;
|
||
begin
|
||
y := 0;
|
||
d := 1;
|
||
ok := false;
|
||
for i := 0 to 7 do begin
|
||
k := Pos (b[i], Digit);
|
||
if k = 0 then exit;
|
||
y := y + (k-1) * d;
|
||
if i < 7 then d := 16 * d;
|
||
end; { for }
|
||
ok := true;
|
||
BCDtoDec := y;
|
||
end; (* bcdtodec *)
|
||
{}
|
||
{}
|
||
procedure TEST;
|
||
var i : byte;
|
||
b : BCDType;
|
||
x : longint;
|
||
ok : boolean;
|
||
s : string;
|
||
begin
|
||
s := '12';
|
||
StrToBCD (s, b);
|
||
write ('The BCD value : ');
|
||
for i := 7 downto 0 do write (b[i], ' ');
|
||
writeln;
|
||
x := BCDtoDec (b, ok);
|
||
if ok then writeln ('is ', x, ' as an ordinary decimal')
|
||
else writeln ('Error in BCD');
|
||
end; (* test *)
|
||
{}
|
||
begin TEST; end.
|
||
|
||
Next we can ask, what if the BCD value is given as an integer.
|
||
Simple, first convert the integer into a string. For example in
|
||
the procedure TEST put
|
||
Str (12, s);
|
||
|
||
Finally, what about converting an ordinary decimal to the
|
||
corresponding BCD but given also as a decimal variable. For example
|
||
18 --> 12?
|
||
function LHEXFN (decimal : longint) : string;
|
||
const hexDigit : array [0..15] of char = '0123456789ABCDEF';
|
||
var i : byte;
|
||
s : string;
|
||
begin
|
||
FillChar (s, SizeOf(s), ' ');
|
||
s[0] := chr(8);
|
||
for i := 0 to 7 do
|
||
s[8-i] := HexDigit[(decimal shr (4*i)) and $0F];
|
||
lhexfn := s;
|
||
end; (* lhexfn *)
|
||
{}
|
||
function DecToBCD (x : longint; var ok : boolean) : longint;
|
||
const Digit : array [0..9] of char = '0123456789';
|
||
var hexStr : string;
|
||
var i, k : byte;
|
||
y, d : longint;
|
||
begin
|
||
hexStr := LHEXFN(x);
|
||
y := 0;
|
||
d := 1;
|
||
ok := false;
|
||
for i := 7 downto 0 do begin
|
||
k := Pos (hexStr[i+1], Digit);
|
||
if k = 0 then exit;
|
||
y := y + (k-1) * d;
|
||
if i > 0 then d := 10 * d;
|
||
end; { for }
|
||
ok := true;
|
||
DecToBCD := y;
|
||
end; (* dectobcd *)
|
||
{}
|
||
procedure TEST2;
|
||
var i : byte;
|
||
x10 : longint;
|
||
xBCD : longint;
|
||
ok : boolean;
|
||
begin
|
||
x10 := 18;
|
||
writeln ('The ordinary decimal value : ', x10);
|
||
xBCD := DecToBCD (x10, ok);
|
||
if ok then writeln ('is ', xBCD, ' as a binary coded decimal')
|
||
else writeln ('Error in BCD');
|
||
end; (* test2 *)
|
||
{}
|
||
begin TEST; end.
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:01:02 1996
|
||
Subject: Copying with TP
|
||
|
||
62. *****
|
||
Q: How can I copy a file in a Turbo Pascal program?
|
||
|
||
A: Here is the code. Take a close look. It has some instructive
|
||
features besides the copying, like handling the filemode and using
|
||
dynamic variables (using pointers). Note that since the buffer for
|
||
the copying is places on the heap you must reserve enough heap. For
|
||
example you might have {$M 16384,0,102400}.
|
||
procedure SAFECOPY (fromFile, toFile : string);
|
||
type bufferType = array [1..65535] of char;
|
||
type bufferTypePtr = ^bufferType; { Use the heap }
|
||
var bufferPtr : bufferTypePtr; { for the buffer }
|
||
f1, f2 : file;
|
||
bufferSize, readCount, writeCount : word;
|
||
fmSave : byte; { To store the filemode }
|
||
begin
|
||
bufferSize := SizeOf(bufferType);
|
||
if MaxAvail < bufferSize then exit; { Assure there is enough memory }
|
||
New (bufferPtr); { Create the buffer, on the heap }
|
||
fmSave := FileMode; { Store the filemode }
|
||
FileMode := 0; { To read also read-only files }
|
||
Assign (f1, fromFile);
|
||
{$I-} Reset (f1, 1); {$I+} { Note the record size 1, important! }
|
||
if IOResult <> 0 then exit; { Does the file exist? }
|
||
Assign (f2, toFile);
|
||
{$I-} Reset (f2, 1); {$I+} { Don't copy on an existing file }
|
||
if IOResult = 0 then begin close (f2); exit; end;
|
||
{$I-} Rewrite (f2, 1); {$I+} { Open the target }
|
||
if IOResult <> 0 then exit;
|
||
repeat { Do the copying }
|
||
BlockRead (f1, bufferPtr^, bufferSize, readCount);
|
||
{$I-} BlockWrite (f2, bufferPtr^, readCount, writeCount); {$I+}
|
||
if IOResult <> 0 then begin close (f1); exit; end;
|
||
until (readCount = 0) or (writeCount <> readCount);
|
||
writeln ('Copied ', fromFile, ' to ', toFile,
|
||
' ', FileSize(f2), ' bytes');
|
||
close (f1); close (f2);
|
||
FileMode := fmSave; { Restore the original filemode }
|
||
Dispose (bufferPtr); { Release the buffer from the heap }
|
||
end; (* safecopy *)
|
||
|
||
Of course a trivial solution would be to invoke the MS-DOS copy
|
||
command using the Exec routine. (See the item "How do I execute an
|
||
MS-DOS command from within a TP program?")
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:01:03 1996
|
||
Subject: C modules in TP
|
||
|
||
63. *****
|
||
Q: How can I use C code in my Turbo Pascal program?
|
||
|
||
A: I have very little information on this question, since I do not
|
||
program in C myself. However in reading Turbo Pascal textbooks I
|
||
have come across a couple of references I can give. They are Edward
|
||
Mitchell (1993), Borland Pascal Developer's Guide, pp. 60-64, and
|
||
Stoker & Ohlsen (1989), Turbo Pascal Advanced Techniques, Ch 4.
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:01:04 1996
|
||
Subject: Using Turbo Profiler
|
||
|
||
64. *****
|
||
Q: How do I get started with the Turbo Profiler?
|
||
|
||
A: Borland's separate Turbo Profiler is a powerful tool for
|
||
improving program code and enhancing program performance, but far
|
||
from an easy to use. It is an advanced tool. In fact setting it up
|
||
the first time is almost a kind of detective work.
|
||
Let's walk through the steps with Turbo Profiler version 1.01 to
|
||
see where a running Turbo Pascal program takes its time.
|
||
Assume a working directory r:\
|
||
1. Copy the target .PAS file to r:\
|
||
2. Compile it with TURBO.EXE using the following Compiler and
|
||
Debugger options. The standalone debugging option is crucial.
|
||
Code generation
|
||
[ ] Force far calls [X] Word align data
|
||
[ ] Overlays allowed [ ] 286 instructions
|
||
Runtime errors Syntax options
|
||
[ ] Range checking [X] Strict var-strings
|
||
[X] Stack checking [ ] Complete boolean eval
|
||
[ ] I/O checking [X] Extended syntax
|
||
[ ] Overflow checking [ ] Typed @ operator
|
||
[ ] Open parameters
|
||
Debugging
|
||
[X] Debug information Numeric processing
|
||
[X] Local symbols [ ] 8087/80287
|
||
[ ] Emulation
|
||
Debugging Display swapping
|
||
[X] Integrated ( ) None
|
||
[X] Standalone () Smart
|
||
( ) Always
|
||
3) Call TPROF.EXE
|
||
4) Load the .EXE file produced by compilation in item 2.
|
||
5) Choose from the TPROF menus
|
||
Statistics
|
||
Profiling options...
|
||
Profile mode
|
||
() Active ( ) Passive
|
||
Run count
|
||
1
|
||
Maximum areas
|
||
200
|
||
6) Choose from the TPROF menus
|
||
Options
|
||
Save options...
|
||
[X] Options
|
||
[ ] Layout
|
||
[ ] Macros
|
||
Save To
|
||
r:\tfconfig.tf
|
||
7) Press Alt-F10 for the Local Menu. Choose
|
||
Add areas
|
||
All routines
|
||
and so on.
|
||
8) Choose Run from the TPROF menus (or F9)
|
||
9) Choose from the TPROF menus
|
||
Print
|
||
Options...
|
||
Width
|
||
80
|
||
Height
|
||
9999
|
||
( ) Printer ( ) Graphics
|
||
() File () ASCII
|
||
Destination File
|
||
r:\report.lst
|
||
10) Print
|
||
Module...
|
||
All modules
|
||
Statistics
|
||
Overwrite
|
||
Also see Edward Mitchell (1993), Borland Pascal Developer's Guide.
|
||
It has a a very instructive chapter "Program Optimization" on the
|
||
Turbo Profiler. The material in the Turbo Profiler manual is so
|
||
complicated that additional guidance like Mitchell's is very much
|
||
needed.
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:01:05 1996
|
||
Subject: Detecting shift status
|
||
|
||
65. *****
|
||
Q: How can I detect if the shift/ctrl/alt etc key is pressed? I
|
||
know how to get the scan codes with the ReadKey function, but I
|
||
can't find the procedure for detecting these keys.
|
||
|
||
A: Detecting pressing the special keys or getting the toggle status
|
||
cannot be done with ReadKey. You'll need to access the Keyboard
|
||
Flags Byte at $0040:$0017. You can do this either by a direct "Mem"
|
||
access, or using interrupt $16 function $02. For more details
|
||
including the bitfields for the shift flags see in Ralf Brown's
|
||
interrupt list ftp://garbo.uwasa.fi/pc/programming/inter48a.zip (or
|
||
whatever is the current version). For example to see if the alt key
|
||
is pressed you can use
|
||
uses Dos;
|
||
function ALTDOWN : boolean;
|
||
var regs : registers;
|
||
begin
|
||
FillChar (regs, SizeOf(regs), 0);
|
||
regs.ah := $02;
|
||
Intr ($16, regs);
|
||
altdown := (regs.al and $08) = $08;
|
||
end;
|
||
For the enhanced keyboard flags see interrupt $16 function $12. It
|
||
can distinguish also between the right and the left alt and ctlr
|
||
keys.
|
||
A tip from Martijn Leisink martijnl@sci.kun.nl. Be careful [if
|
||
you use the $0040:$0017 memory position to set a toggle]: On several
|
||
computers you have to call int 16h after the new setting is shown by
|
||
the LED's on the keyboard. Not doing so might give the user wrong
|
||
information.
|
||
A tip from Dr John Stockton jrs@dclf.npl.co.uk. Going via a
|
||
BytePointer set to Ptr(Seg0040, $0017) is almost as easy as "Mem",
|
||
and also works in Protected mode.
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:01:06 1996
|
||
Subject: Base 10 logarithm
|
||
|
||
66. *****
|
||
Q: How do I get a base 10 logarithm in TP?
|
||
|
||
A: Just define
|
||
function log (x : real) : real;
|
||
begin log := ln(x) / ln(10); end;
|
||
This result is based on some elementary math. By definition
|
||
y = log(x) in base 10 is equivalent to x = 10^y (where the ^
|
||
indicates an exponent). Thus ln(x) = y ln(10) and hence
|
||
y = ln(x) / ln(10).
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:01:07 1996
|
||
Subject: Replacing Delay procedure
|
||
|
||
67. *****
|
||
Q: If Delay procedure does not work properly, how do I fix it?
|
||
|
||
A: The Delay procedure in the Crt unit delays a specified number of
|
||
milliseconds. It is declared as "procedure Delay(MS: Word);". There
|
||
are two problems. The procedure requires using the Crt unit and
|
||
there is a bug in it in TP 6.0, at least. The alternative is to use
|
||
the procedure GetTime(var Hour, Minute, Second, Sec100: Word) as
|
||
shown by the skeleton below
|
||
GetTime (...)
|
||
initialTime := ...
|
||
repeat
|
||
GetTime (...)
|
||
interval := ... - initialTime;
|
||
until interval >= YourDelay;
|
||
There are two things you will have to see to. You will have to
|
||
convert the time to sec100, and you will have to take care of the
|
||
possibility of the interval spanning the midnight. If you do not
|
||
wish to program the alternative Delay procedure yourself, you can
|
||
use "DOSDELAY Delay without needing the Crt unit" from TSUNTD.TPU
|
||
from ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip.
|
||
|
||
A2: Dr John Stockton jrs@dclf.npl.co.uk suggested procedure that is
|
||
expanded below. It has the advantage of being concise and working in
|
||
the protected mode. The disadvantage is that it requires a later TP
|
||
version. The solution is quite instructive.
|
||
uses Dos;
|
||
{... John's procedure ...}
|
||
procedure WAIT (SecondsDelay : real) ;
|
||
Var Tptr : ^longint ; Finish : longint ;
|
||
begin
|
||
Tptr := Ptr(Seg0040, $006C) ;
|
||
Finish := Tptr^ + Round(18.2*SecondsDelay) ;
|
||
repeat until Tptr^ > Finish ;
|
||
end;
|
||
{... now let's test it ...}
|
||
var h1, m1, s1, sa100 : word;
|
||
h2, m2, s2, sb100 : word;
|
||
begin
|
||
GetTime (h1, m1, s1, sa100);
|
||
WAIT (3);
|
||
GetTime (h2, m2, s2, sb100);
|
||
writeln (h1, ':', m1, ':', s1, '.' ,sa100);
|
||
writeln (h2, ':', m2, ':', s2, '.' ,sb100);
|
||
end.
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:01:08 1996
|
||
Subject: TP program memory requirement
|
||
|
||
68. *****
|
||
Q: How much memory will my TP program require?
|
||
|
||
A: Get MAPMEM.EXE from ftp://garbo.uwasa.fi/pc/memutil/tsrcom35.zip
|
||
and put the following code within your Turbo Pascal program:
|
||
Program faq;
|
||
uses Dos;
|
||
:
|
||
SwapVectors;
|
||
Exec (GetEnv('comspec'), '/c mapmem');
|
||
Swapvectors;
|
||
Then you'll see a MAPMEM output something like this
|
||
Psp Cnt Size Name Command Line Hooked Vectors
|
||
---- --- ------ ---------- ------------------- --------------
|
||
2 26,896 DOS
|
||
0694 2 3,392 COMMAND 2E
|
||
1 64 ---free---
|
||
0776 2 1,488 MARK scrollit
|
||
07D6 2 70,816 FAQ FF
|
||
1923 3 2,752 command 22 23 24
|
||
19D2 2 549,712 ---free---
|
||
655,344 ---total--
|
||
The memory requirement of your program FAQ.PAS is 70,816. Do not
|
||
confuse this figure with the physica size of your program. The
|
||
memory requirement affected among other things by the Memory
|
||
Allocation Sizes Directive. For example you might have
|
||
{$M 16384,0,50000}
|
||
|
||
-Date: Sun, 12 Jun 1994 10:22:18
|
||
-From: dmurdoch@mast.queensu.ca (Duncan Murdoch)
|
||
-Newsgroups: comp.lang.pascal
|
||
-Subject: Re: How much memory will my TP program require?
|
||
|
||
I think this is a hard question, and probably needs a longer
|
||
answer than you gave. Yours isn't quite right, because TP will
|
||
allocate memory that it doesn't need if you set the heapmax
|
||
parameter too high. Your program will run in less memory than
|
||
MAPMEM reports. Here's a quick attempt at it:
|
||
TP DOS programs use memory in 4 or 5 blocks: fixed code, static
|
||
data, the stack, sometimes overlaid code, and the heap. TP Windows
|
||
programs add a local heap to this list, but don't use overlays. The
|
||
discussion below deals with real mode DOS programs.
|
||
The size of the code is determined by which procedures and
|
||
functions you use in your program. In DOS, if you don't use
|
||
overlays, this is all fixed code, and the size is reported as "Code
|
||
size" in the Compile| Information listing in the IDE. The ways to
|
||
reduce it are to use fewer procedures or make them smaller, or move
|
||
them to overlays.
|
||
Static data consists of all the global variables and typed
|
||
constants in every unit. It is reported as "Data size" in the
|
||
Compile|Information listing. You can reduce it by declaring fewer
|
||
or smaller variables.
|
||
If you use the $O directive to move code to overlays, then those
|
||
units won't count as part of your fixed code needs. You will need
|
||
an overlay buffer at run-time; by default, it's the size of the
|
||
largest unit you use, but normally you'll change the size with
|
||
OvrSetBuf. It's difficult to work out the best size of this block
|
||
except by trial and error: if your program spends too much time
|
||
swapping, then make it larger; if you run out of memory, make it
|
||
smaller. You'll need to use the .MAP file (see the Options| Linker
|
||
dialog to create one) to find the size of each unit. Remember to
|
||
subtract the size of overlaid units from the reported "Code size"
|
||
when working out the size of fixed code.
|
||
The stack is used for local variables in procedures. Its size is
|
||
controlled by the first parameter to the $M directive; the default
|
||
size is 16K. It's hard to predict exactly how much stack space your
|
||
program will use. One way is to keep reducing the value until your
|
||
program aborts with a stack overflow, then use a slightly larger
|
||
value. Another way is to fill the stack with a fixed value at the
|
||
start of your program, and at the end, see how many values were
|
||
changed. Again, it's a good idea to allow for a margin of safety,
|
||
because hardware interrupts will use this space, and their size is
|
||
hard to predict.
|
||
The heap is where New and Getmem get their allocated memory. The
|
||
size is controlled by the 2nd and 3rd parameters to the $M
|
||
directive. The heapmin value will always be allocated; if extra
|
||
memory is available, your program will ask for as much as possible,
|
||
up to heapmax. If not enough memory is available to load all your
|
||
fixed code, data, stack and heapmin, DOS will refuse to load your
|
||
program. You have nearly complete control over the size of the heap
|
||
that you need, determined by how much you use New and Getmem. The
|
||
only exception is that some of the standard units use heap space;
|
||
GRAPH and all the TurboVision units are examples. To find how much
|
||
your program actually uses, you can reduce Heapmax until it fails,
|
||
fill the heap with a special value and look for changes, or monitor
|
||
the value of HeapPtr as your program progresses.
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:01:09 1996
|
||
Subject: Detecting a CD-ROM drive?
|
||
|
||
69. *****
|
||
Q: How to detect if a drive is a CD-ROM drive?
|
||
|
||
A: There are several methods to do this. Here is one option.
|
||
(* Is a drive a CD-ROM with MSCDEX driver installed *)
|
||
function CDROMFN (drive : char) : boolean;
|
||
var regs : registers;
|
||
begin
|
||
cdromfn := false;
|
||
if swap(DosVersion) < $0200 then exit;
|
||
drive := UpCase(drive);
|
||
if (drive < 'A') or (drive > 'Z') then exit;
|
||
FillChar (regs, SizeOf(regs), 0);
|
||
regs.cx := ord(drive) - ord('A');
|
||
regs.ax := $150B;
|
||
Intr ($2F, regs);
|
||
cdromfn := (regs.ax <> 0) and (regs.bx = $ADAD);
|
||
end; (* cdromfn *)
|
||
The other relevant $2F interrupt functions you can use are $1500,
|
||
$1501, and in particular $150D.
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:01:10 1996
|
||
Subject: Array of chars into string
|
||
|
||
70. *****
|
||
Q: How do I convert an array of characters to a string? More
|
||
specifically, I haven't been able to convert an array of characters
|
||
into a string, so that I can write it to a file. The only way I have
|
||
been able to do it, is writing 1 char at a time.
|
||
|
||
A: Carefully study these two simple test examples. Note the
|
||
difference in the array's dimensions in the tests.
|
||
|
||
type atype = array [0..20] of char;
|
||
type stype = string[20];
|
||
var s : stype;
|
||
a : atype absolute s;
|
||
begin
|
||
FillChar (a, SizeOf(a), '*');
|
||
s[0] := chr(20);
|
||
writeln (s);
|
||
end.
|
||
|
||
type atype = array [1..20] of char;
|
||
var s : string;
|
||
a : atype;
|
||
begin
|
||
FillChar (a, Sizeof(a), '*');
|
||
Move (a, s[1], 20);
|
||
s[0] := chr(20);
|
||
writeln (s);
|
||
end.
|
||
|
||
Of course, you could also assign the array's characters one by one
|
||
to the string using a simple for loop (left as an exercise), but the
|
||
above methods are more efficient.
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:01:11 1996
|
||
Subject: Graphics programming primer
|
||
|
||
71. *****
|
||
Q: How do I get started with graphics programming?
|
||
|
||
A:
|
||
(* This simple test shows the rudiments of getting started with Turbo
|
||
Pascal graphics programming *)
|
||
uses Crt, Graph;
|
||
var grDriver : integer;
|
||
grMode : integer;
|
||
ErrCode : integer;
|
||
i, j : integer;
|
||
xm, ym : integer;
|
||
const CharSize : integer = 3;
|
||
begin
|
||
{ Request graphics driver autodetection }
|
||
grDriver := Detect;
|
||
{ Initialize graphics system and put hardware into graphics mode }
|
||
{ The relevant .bgi driver is needed in the current directory
|
||
for example egavga.bgi }
|
||
InitGraph (grDriver, grMode, ' ');
|
||
{ Return an error code for the previous graphic operation }
|
||
ErrCode := GraphResult;
|
||
{ Test for initialialization success }
|
||
if ErrCode <> grOk then begin
|
||
Writeln ('Graphics error:', GraphErrorMsg(ErrCode)); halt; end;
|
||
{ Clear the output device and home the current pointer }
|
||
ClearDevice;
|
||
{}
|
||
{ Use your own coordinates }
|
||
xm := Round (GetMaxX / 100.0);
|
||
ym := Round (GetMaxY / 100.0);
|
||
{}
|
||
{ Set the current line width and style, optional }
|
||
SetLineStyle (SolidLn, 0, ThickWidth);
|
||
{ Set the drawing color }
|
||
SetColor (Yellow);
|
||
{ Draw a line }
|
||
Line (70*xm, 50*ym, 90*xm, 80*ym);
|
||
{}
|
||
{ Drawing bars }
|
||
{ Set the fill pattern and color }
|
||
SetFillStyle (SolidFill, Red);
|
||
Bar (0, 0, 25*xm, 25*ym);
|
||
{}
|
||
SetColor (Magenta);
|
||
SetFillStyle (SolidFill, Blue);
|
||
Bar3D (30*xm, 20*ym, 50*xm, 60*ym, 8*xm, TopOn);
|
||
{}
|
||
{ Writing text in the graphics mode }
|
||
{ Set the drawing color }
|
||
SetColor (LightCyan);
|
||
{ Set the current background color }
|
||
SetBkColor (Black);
|
||
{ Set style for text output in graphics mode }
|
||
SetTextStyle(DefaultFont, HorizDir, CharSize);
|
||
OutTextXY (0, 80*ym, 'Press any key');
|
||
{}
|
||
repeat until KeyPressed;
|
||
{}
|
||
{ Restore the original screen mode before graphics was initialized }
|
||
RestoreCrtMode;
|
||
writeln ('That''s all folks');
|
||
{ Shut down the graphics system }
|
||
CloseGraph;
|
||
end.
|
||
For an example what you can do with graphics, see
|
||
111673 Oct 8 1993 ftp://garbo.uwasa.fi/pc/ts/tsdemo16.zip
|
||
tsdemo16.zip Assorted graphics demonstrations of functions etc
|
||
(or whatever is the current version).
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:01:12 1996
|
||
Subject: Sorting it out
|
||
|
||
72. *****
|
||
Q: Where to I find the different sorting source codes?
|
||
|
||
A: I'll answer very briefly by giving two references:
|
||
303771 May 2 1991 ftp://garbo.uwasa.fi/pc/turbopas/nrpas13.zip
|
||
nrpas13.zip Numerical Recipes Pascal shareware version
|
||
and
|
||
Gary Martin (1992), Turbo Pascal, Theory and Practice of Good
|
||
Programming, Chapter 15.
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:01:13 1996
|
||
Subject: TP units
|
||
|
||
73. *****
|
||
Q: A beginner's how to write and compile units.
|
||
|
||
A1: Many of the text-books in the bibliography section of this FAQ
|
||
discuss using units in Turbo Pascal. For example see Tom Swan
|
||
(1989), Mastering Turbo Pascal 5.5, Chapters 9 and 10 for a more
|
||
detailed discussion than the rudiments given in the current item.
|
||
Likewise see your Turbo Pascal (7.0) User's Guide Chapter 6, "Turbo
|
||
Pascal units".
|
||
You can and need to write your own units if you need recurring or
|
||
common routines in your programs and/or your program becomes so big
|
||
that it cannot be handled as a single entity.
|
||
A Turbo Pascal unit is a separate file which you compile. The
|
||
following trivial example to calculate the sum of two reals
|
||
illustrates the basic structure of a unit.
|
||
{ The name of this file must be faq73.pas to correspond. }
|
||
unit faq73;
|
||
{}
|
||
{ The interface section lists definitions and routines that are }
|
||
{ available to the other programs or units. }
|
||
interface
|
||
function SUMFN (a, b : real) : real;
|
||
{}
|
||
{ The implementation section contains the actual unit program }
|
||
implementation
|
||
function SUMFN (a, b : real) : real;
|
||
begin
|
||
sumfn := a + b;
|
||
end;
|
||
{}
|
||
end.
|
||
When you compile the file FAQ73.PAS a unit FAQ73.TPU results. Next
|
||
an example utilizing the faq73 unit in the main program.
|
||
uses faq73;
|
||
{}
|
||
procedure TEST;
|
||
var x, y, z : real;
|
||
begin
|
||
x := 12.34;
|
||
y := 56.78;
|
||
z := SUMFN (x, y);
|
||
writeln (z);
|
||
end;
|
||
{}
|
||
begin
|
||
TEST;
|
||
end.
|
||
|
||
A2: Most often you would be compiling a Turbo Pascal program
|
||
using the IDE (Integrated Development Environment). If you have
|
||
precompiled units you must see to it that you have informed the IDE
|
||
of the path to them.
|
||
Press F10 and invoke the "Options" menu (or press alt-O). Select
|
||
"Directories...". Press tab two times to get to "Unit directories"
|
||
and edit the path accordingly. Here is what I have entered myself
|
||
EXE & TPU directory r:\
|
||
Include directories r:\
|
||
Unit directories f:\progs\turbo70\tpu70
|
||
Object directories f:\progs\turbo70\tpu70
|
||
As you see I keep all my precompiled Turbo Pascal 7.0 units in the
|
||
f:\progs\turbo70\tpu70 directory.
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:01:14 1996
|
||
Subject: Beginners' pointers
|
||
|
||
74. *****
|
||
Q: What are and how do I use pointers?
|
||
|
||
A: This is a beginner's simplified introduction. A pointer is a
|
||
variable type used to hold the address of another variable, that is
|
||
to point to it. Pointers are used to
|
||
1) To refer to and manipulate variables indirectly.
|
||
2) In Turbo Pascal to obtain access to the heap storage area, which
|
||
is not restricted to 64Kbytes.
|
||
Consider the following example
|
||
{$M 16384,0,80000}
|
||
var yPtr : ^real;
|
||
begin
|
||
New(yPtr);
|
||
yPtr^ := 3.14159;
|
||
writeln ('2 times pi = ', 2.0 * yPtr^);
|
||
Dispose(yPtr);
|
||
yPtr := nil;
|
||
end.
|
||
Before we can discuss pointers we have to consider some rudiments of
|
||
what a kind of a memory model a compiled Turbo Pascal program uses.
|
||
This is a highly simplified presentation. For a more detailed
|
||
presentation of the TP memory model see for example Tischer (1990b).
|
||
+-------------------------+
|
||
| Heap |
|
||
|-------------------------|
|
||
| Data Segment |
|
||
|-------------------------|
|
||
| Code |
|
||
|-------------------------|
|
||
| Program Segment Prefix |
|
||
+-------------------------+
|
||
When you write and compile a Turbo Pascal program it usually
|
||
consists of (this is a simplification!) of the three lowest parts.
|
||
When you define a global variable, it goes to the Data Segment. For
|
||
example defining at the beginning of your program
|
||
var x : real;
|
||
requires 6 bytes from the data segment. (Local variables are placed
|
||
on the stack.)
|
||
Now, the catch is that because of the underlying 16-bit nature of
|
||
MS-DOS, the size of the data segment cannot exceed 64Kb. On occasion
|
||
the 64Kb is insufficient. However, if you use pointers, the
|
||
corresponding variable values are held on the heap instead of the
|
||
data segment or the stack. Before you can use the heap, you have to
|
||
reserve it for your program. The following compiler directive makes
|
||
a heap of 80000 bytes available to your program {$M 16384,0,80000}.
|
||
(The syntax is {$M Stack size, Low heap limit, High heap limit}).
|
||
With pointers you do not refer to a variable directly, but you
|
||
point to it. For example, define
|
||
var yPtr : ^real;
|
||
Before you can use this pointer, you have to create this new dynamic
|
||
variable as follows:
|
||
New(yPtr);
|
||
The New(yPtr) statement "Creates a new dynamic variable and sets a
|
||
pointer variable to point to it." This pointer, yPtr, will point to
|
||
the actual value, which the program puts on the heap. In your
|
||
program you can write, for example
|
||
yPtr^ := 3.14159;
|
||
Think about the difference between yPtr and yPtr^. The former
|
||
contains the value of the memory address where you now have put the
|
||
value 3.14159. The latter gives that value. Hence yPtr^ can be used
|
||
like any ordinary real variable. The difference is that it is on the
|
||
heap, not on the data segment (or stack). Thus you can now use this
|
||
pointer. For example you n write
|
||
writeln ('2 times pi = ', 2.0 * yPtr^);
|
||
When you do not need the pointer any more in your program you can
|
||
dispose of it to release the memory allocated for other purposes:
|
||
Dispose(yPtr);
|
||
yPtr := nil;
|
||
"After a call to Dispose, the value of yPtr is undefined and it is
|
||
an error to reference yPtr. The reserved word nil denotes a pointer
|
||
type constant that does not point to anything." Setting yPtr := nil
|
||
is just good programming practice, because then you can later easily
|
||
test whether the pointer is available or not. Disposing of a pointer
|
||
within your program is not necessary unless the amount of memory is
|
||
a critical consideration in your program. The heap will be released
|
||
when your program terminates.
|
||
To recount. What yPtr actually contains is the memory address of
|
||
the value on the heap. When you write yPtr^, the caret indicates
|
||
that you do not mean the pointer itself, but the pointed memory
|
||
location in the heap. In this example that memory location in the
|
||
heap was made to contain 3.14159.
|
||
You can also define the pointer types. Our second example
|
||
illustrates. It displays the squares from one to ten.
|
||
{$M 16384,0,80000}
|
||
type arrayType = array [1..10] of real;
|
||
type arrayPtrType = ^arrayType;
|
||
var A : arrayPtrType;
|
||
i : integer;
|
||
begin
|
||
if SizeOf(arrayType) > MaxAvail then begin
|
||
writeln ('Out of memory');
|
||
halt;
|
||
end;
|
||
New(A);
|
||
for i := 1 to 10 do A^[i] := i*i;
|
||
writeln (A^[9]);
|
||
end.
|
||
For an actual application using pointers, see the item "How can I
|
||
copy a file in a Turbo Pascal program?"
|
||
--------------------------------------------------------------------
|
||
|
||
From ts@uwasa.fi Thu Mar 7 00:01:15 1996
|
||
Subject: Reading errorlevel
|
||
|
||
75. *****
|
||
Q: How can I read another program's errorlevel value in TP?
|
||
|
||
A: This question is best answered by an example. Here is a very
|
||
elementary program that returns errorlevel 14 on exiting.
|
||
program faq2;
|
||
begin
|
||
writeln ('Hello world...');
|
||
halt(14);
|
||
end.
|
||
Below is the program that calls FAQ2.EXE and detects its errorlevel.
|
||
{$M 2000,0,0}
|
||
uses Dos;
|
||
begin
|
||
SwapVectors;
|
||
Exec ('r:\faq2.exe', ''); (* Execution *)
|
||
SwapVectors;
|
||
WriteLn('...back from Exec');
|
||
if DosError <> 0 then
|
||
WriteLn('Dos error #', DosError)
|
||
else
|
||
WriteLn('Success; child process errorlevel = ', lo(DosExitCode));
|
||
end.
|
||
The output should be
|
||
Hello world...
|
||
...back from Exec
|
||
Success; child process errorlevel = 14
|
||
--------------------------------------------------------------------
|
||
|