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 --------------------------------------------------------------------