Copyright (c) 1993-1996 by Timo Salmi All rights reserved FAQPAS.TXT 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. If you are an experienced Turbo Pascal programmer with Turbo Pascal material you would like to circulate publicly world-wide, you are welcome to submit your material to the Garbo MS-DOS archives at the University of Vaasa. To do that you *FIRST* must *CAREFULLY* read the uploading instructions and the formal requirements in the file ftp://garbo.uwasa.fi/pc/UPLOAD.INF. You are also welcome to contact me by email for these instructions if you are not familiar with downloading them from the Garbo archives. -------------------------------------------------------------------- 1) How do I disable or capture the break key in Turbo Pascal? 2) How do I get a printed documentation of my students' TP runs? 3) What is the code for the weekday of a given date? 4) Need a program to format Turbo Pascal source code consistently 5) Can someone give me advice for writing a tsr program? 6) Why can't I read / write the com ports? 7) What are interrupts and how to use them in Turbo Pascal? 8) Should I upgrade my Turbo Pascal version? 9) How do I execute an MS-DOS command from within a TP program? 10) How is millisecond timing done? 11) How can I customize the text characters to my own liking? 12) How to find the files in a directory and subdirectories? 13) I need a power function but there is none in Turbo Pascal. 14) How can I create arrays that are larger than 64 kilobytes? 15) How can I test that the printer is ready? 16) How can I clear the keyboard type-ahead buffer? 17) How can I utilize expanded memory (EMS) in my programs? 18) How can I obtain the entire command line? 19) How do I redirect text from printer to file in my TP program? 20) Turbo Pascal is for wimps. Use standard Pascal or C instead? 21) How do I turn the cursor off? 22) How to find all roots of a polynomial? 23) What is all this talk about "Pascal homework on the net"? 24) How can I link graphics drivers directly into my executable? 25) How can I trap a runtime error? 26) How to get ansi control codes working in Turbo Pascal writes? 27) How to evaluate a function given as a string to the program? 28) How does one detect whether input (or output) is redirected? 29) How does one set the 43/50 line text mode? 30) How can I assign a value to an environment variable in TP? 31) How does one store, and then restore the original screen? 32) How can I convert a TPU unit of one TP version to another? 33) Which error is e.g. Runtime error 205, etc 34) Why can't I open read-only files? I get "File access denied". 35) How do I obtain high and low parts of a byte variable? 36) How can I set a hi-intensity color background in the text mode? 37) Where can I find a program to convert (Turbo) Pascal to C? 38) How can I read input without echoing to the screen? 39) How can I edit the readln input stream? 40) How can I write (brand) something into my executables? 41) What is wrong with my program? It hangs without a clear pattern? 42) How do I convert a decimal word into a hexadecimal string, etc? 43) How to determine the last drive? 44) How can I put a running clock into my Turbo Pascal program? 45) How to establish if a name refers to a directory or not? 46) How does one disable alt-ctrl-del? 47) How can I test whether a file exists? 48) What is the name of the current Turbo Pascal program? 49) How is the code for rebooting the PC written in Turbo Pascal? 50) How can I write inline code? 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? 76) What are the current Pascal newsgroups on the Usenet news? 77) How do I detect the CapsLock status, how do I turn it on/off? 78) How do I detect if the F11 or F12 key has been pressed? 79) How do I extract (parse) substrings from an input string? 80) How do I find out the size of any kind of a file? 81) How do I format graphics output like in textmode writeln? 82) How do I detect if more than one standard key is pressed down? 83) How can I read a disk's Volume Serial Number? 84) How can I disable and then enable the keyboard in my TP program? 85) How do I get the character device name of the (first) CD-ROM? 86) How do I eject a CD-ROM using a Turbo Pascal program? 87) How do I find out if the ANSI.SYS driver has been loaded? 88) Where do I find Turbo Pascal tutorials and/or good textbooks? 89) How dow I make an executable of my Turbo Pascal source program? 90) How can I quickly read the last byte of a file? -------------------------------------------------------------------- Unless otherwise stated the answers cover versions 4.0, 5.0, 5.5, 6.0 and 7.0 (real mode). The Q&As are not for Turbo Pascal version 3 or earlier. Objects, TVision, Windows, Delphi, etc are not covered. (I do not use them myself.) -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:01 1996 Subject: Disabling or capturing the break key 1. ***** Q: I don't want the Break key to be able to interrupt my TP programs. How is this done? Q2: I want to be able to capture the Break key in my TP program. How is this done? Q3: How do I detect if a certain key has been pressed? (Often, how do I detect, for example, if the CursorUp key has been pressed?) Q4: How do I detect if a cursor key or a function key has been pressed? A: This set of frequently asked questions is basically a case of RTFM (read the f*ing manual). But this feature is, admittedly, not very prominently displayed in the Turbo Pascal reference. (As a general rule we should not use the newsgroups as a replacement for our possibly missing manuals, but enough of this line.) I'll only explain Q and Q2. The other two, Q3 and Q4 should be evident from the example code. There is a CheckBreak variable in the Crt unit, which is true by default. To turn it off use uses Crt; : CheckBreak := false; : Besides turning off break checking this enables you to capture the pressing of the break key as you would capture pressing ctrl-c. In other words you can use e.g. : procedure TEST; var key : char; begin repeat if KeyPressed then begin key := ReadKey; case key of #0 : begin key := ReadKey; case key of #59 : write ('F1 '); #72 : write ('CursUp '); { Deteting these } #75 : write ('CursLf '); { is often asked! } #77 : write ('CursRg '); #80 : write ('CursDn '); else write ('0 ', ord(key), ' '); end; {case} end; #3 : begin {ctrl-c or break} writeln ('Break'); halt(1); end; { Terminate the program, or whatever } #27 : begin write (' '); exit; { Exit test, continue program } end; else write (key, ' '); end; {case} end; {if} until false; end; (* test *) : IMPORTANT: Don't test the ctrl-break feature just from within the TP IDE, because it has ctlr-break handler ("interceptor") of its own and may confuse you into thinking that ctrl-break cannot be circumvented by the method given above. The above example has a double purpose. It also shows the rudiments how you can detect if a certain key has been pressed. This enables you to give input without echoing it to the screen, which is a later FAQ in this collection. This is, however, not all there can be to break checking, since the capturing is possible only at input time. It is also possible to write a break handler to interrupt a TP program at any time. For more details see Ohlsen & Stoker, Turbo Pascal Advanced Techniques, Chapter 7. (For the bibliography, see FAQPASB.TXT in this same FAQ collection). A2: This frequent question also elicits one of the most frequent false answers. It is often suggested erroneously that the relevant code would be uses dos; SetCBreak(false); This is not so. It confuses MS-DOS and TP break checking with each other. SetCBreak(false) will _*NOT*_ disable the Ctrl-Break key for your Turbo Pascal program. What it does is "Sets the state of Ctrl-Break checking in DOS". SetCBreak sets the state of Ctrl+Break checking in DOS. When off (False), DOS only checks for Ctrl+Break during I/O to console, printer, or communication devices. When on (True), checks are made at every system call." This item goes to shows how important it is carefully to check one's code and facts before claiming something. A3: Using the "CheckBreak := false;" method is not the only alternative, however. Here is an example code for disabling Ctrl-Break and Ctrl-C with interrupts uses Dos; var OldIntr1B : pointer; { Ctrl-Break address } OldIntr23 : pointer; { Ctrl-C interrupt handler } answer : string; { For readln test } {$F+} procedure NewIntr1B (flags,cs,ip,ax,bx,cx,dx,si,di,ds,es,bp : word); Interrupt; {$F-} begin end; {$F+} procedure NewIntr23 (flags,cs,ip,ax,bx,cx,dx,si,di,ds,es,bp : word); Interrupt; {$F-} begin end; begin GetIntVec ($1B, OldIntr1B); SetIntVec ($1B, @NewIntr1B); { Disable Ctrl-Break } GetIntVec ($23, OldIntr23); SetIntVec ($23, @NewIntr23); { Disable Ctrl-C } writeln ('Try breaking, disabled'); readln (answer); SetIntVec ($1B, OldIntr1B); { Enable Ctrl-Break } SetIntVec ($23, OldIntr23); { Enable Ctrl-C } writeln ('Try breaking, enabled'); readln (answer); writeln ('Done'); end. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:02 1996 Subject: Directing output also to printer 2. ***** Q: I want to have a printed documentation of my students' Turbo Pascal program exercises. How is all input and output directed also to the printer? A1: Use a screen capturing program to put everything that comes onto the screen into a file, and print the file. See FAQPROGS.TXT in ftp://garbo.uwasa.fi/pc/ts/tsfaqn43.zip (or whatever version number is the current) for more about these programs. Available by anonymous FTP or mail server from garbo.uwasa.fi. A2: See the code in TSPAS.NWS (item: Redirecting writes to the printer) in the ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip (or whatever is the current version number) Turbo Pascal units package (70 = 40, 50, 55, 60, or 70 depending on your TP version). Alternatively use USECON and USEPRN routines in the TSUNTG unit of the same package. +------------------------------------------+ ! To get these and other packages given as ! ! /dir/subdir/name ! ! see the instructions in PD2ANS.TXT ! +------------------------------------------+ A3: But the really elegant solution to the problem of getting a logfile (or a printed list) of a Turbo Pascal run is to rewrite the write(ln) and read(ln) device driver functions. In itself writing such driver redirections is very advanced Turbo Pascal programming, but when the programming has once been done, the system is extremely easy to use as many times as you like. It goes like this. The driver redirections are programmed into a unit (say, tpulog or tpuprn). All that is needed after that is to include the following uses statement into the program (the target program) which has to be logged: uses TPULOG; ( or ) uses TPUPRN; This is all there is to it. Just adding one simple line to the target program. (If you call any other units, "uses tpulog" must come AFTER the system units (e.g. Dos), but BEFORE any which you may define yourself!) The reason that I have named two units here instead of just one in the above example is that the preferred log for the target program may be a logfile or the printer. The better solution of these two is to use the logfile option, and then print it. The reason is simple. If the target program itself prints something, your printout will look confused. The logging also has obvious limitations. It works for standard input and output (read(ln) and write(ln)) only. 1) It does not support graphics, in other words it is for the textmode. 2) It does not support direct (Crt) screen writes. 3) And, naturally it only shows the input and output that comes to the screen. Not any other input or output, such as from or to a file. 4) Furthermore, you are not allowed to reassign input or output. Statements like assign (output, '') will result in a crash, because the rewritten output device redirections are invalidated by such statements. 5) The device on the default drive must not be write protected, since else the logfile cannot be written to it. 6) It does not work for Turbo Pascal 4.0. Despite these restrictions, the method is perfectly suited for logging students' Turbo Pascal escapades. It is advisable first to test and run your target program without "tpulog", so that if you get any strange errors you'll know whether they are caused by the logging. Where to get such a unit. The code can be found in Michael Tischer (1990), Turbo Pascal Internals, Abacus, Section 4.2. Next a few of my own tips on this unit Tischer calls Prot. 1) The code is in incorrect order. The code that is listed on pages 142 - 145 goes between pages 139 and 140. 2) You can change the logfile name (const prot_name) to lpt1 for a printed list of the target program run. In that case it is advisable to include a test for the online status of the printer within Tischer's unit. 3) I see no reason why the two lines in Tischer's interface section couldn't be transferred to the implementation section. Why have any global definitions? But all in all, it works like magic! A4: From: abcscnuk@csunb.csun.edu (Naoto Kimura (ACM)) Subject: Re: Printing a log of students' exercises revisited To: ts@uwasa.fi Date: Fri, 2 Nov 90 20:52:03 pdt [Reproduced with Naoto's kind permission] By the way, several months ago, I had submitted a file (nktools.zip) file on SimTel that contains sources to a unit (LOGGER), which allows logging of I/O going through the standard input and output files, while still being able to use the program interactively. I believe that I also submitted a copy to your site. It was something I put together for use by students here at California State University at Northridge. The source works equally well in all presently available versions of Turbo Pascal. The only requirements are that * you place it as one of the last entries in the USES clause. If there is anything that redirects the standard input and output file variables, you should put that unit before my unit in the USES clause, so that it can see the I/O stream. * Don't use the GotoXY and similar screen display control procedures in the Crt unit and expect it to come out the same way you had it on the display. Since all my unit does is just capture the I/O stream to send it through the normal channels and also to the log file, all screen control information is not sent to the log file. * All I/O you want logged should go through the standard input and output file variables. * Don't close the standard input and output file variables, because it will cause problems. Basically, as far as I have checked, it just causes the logging to stop at that point. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:03 1996 Subject: Code to give the weekday of a date 3. ***** Q: I want code that gives the weekday of the given date. A1: There is a WKDAYFN function in my Turbo Pascal units collection ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip (or whatever version number is the latest, and where 70 is 40 50 55 60 and 70) Turbo Pascal units collection to give the modern weekday based on Zeller's congruence. Available by anonymous FTP or mail server from garbo.uwasa.fi. Also you can find a more extensive Julian and Gregorian weekday algorithm with source code in Dr.Dobb's Journal, June 1989, p. 148. Furthermore Press & Flannery & al (1986), Numerical Recipes, Cambridge University Press, present a weekday code. The Numerical Recipes codes are available as ftp://garbo.uwasa.fi/pc/turbopas/nrpas13.zip (big, 404k!). A2: Some will recommend the following kludge. Store the current date, change it, and let MS-DOS get you the weekday. Don't use it! It is a bad suggestion. On top of being sloppy programming, there are several snags. The trick works only for years 1980-2079. A crash the program may leave the clock at a wrong date. And even if multitasking is rare, in a multitasking environment havoc may result for the other tasks. And you may have a TSR that requires the correct date, etc. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:04 1996 Subject: Pretty printers (or uniform code) 4. ***** Q: Where can I find a program that formats my (or my students') Turbo Pascal code in a consistent matter. A: What you are asking for is often called "a pretty printer". TurboPower Software's (the usual disclaimer applies) commercial Turbo Analyst has a facility for this with many options. There are also PD and shareware pretty printers, such as : 16830 Nov 28 1989 ftp://garbo.uwasa.fi/pc/turbopas/ppdk50.zip ppdk50.zip Pascal Prettypringting Program, tweak, D.Kirschbaum : 10015 Mar 29 1991 ftp://garbo.uwasa.fi/pc/turbopas/ppp.zip ppp.zip Pretty Print Pascal, with Turbo Pascal 5+ source, M.Bless : 38021 Nov 5 1994 ftp://garbo.uwasa.fi/pc/turbopas/bp7sb104.zip bp7sb104.zip Borland and TP Source Beautifier, crippleware, J.Ferincz : 25100 Jul 4 1992 ftp://garbo.uwasa.fi/pc/turbopas/epb232.zip epb232.zip Ed's Pascal Beautifier, E.Lee : and others at garbo.uwasa.fi available by anonymous FTP or mail server. See ftp://garbo.uwasa.fi/pc/INDEX.ZIP for the list of the files. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:05 1996 Subject: How to write TSR programs 5. ***** Q: Can someone give me advice for writing a tsr program? A: Writing a terminate and stay resident program can be considered advanced programming and is beyond the scope of an electronic message with limited space. Instead, here are some references to Turbo Pascal books and papers which have a coverage of the subject. Stephen O'Brien, Turbo Pascal, The Complete Reference, Chapter 16; Stephen O'Brien, Turbo Pascal, Advanced Programmer's Guide, Chapter 6; Michael Tischer, Turbo Pascal Internals, Chapter 11 (a definite bible of TP programming!); Michael Tischer (1992), PC Intern System Programming, Chapter 32; Michael Yester, Using Turbo Pascal, Chapter 19; Kent Pottebaum, "Creating TSR Programs with Turbo Pascal", Dr. Dobb's Journal, May 1989 and June 1989; Kris Jamsa, Dos Power User's Guide, pp. 649-; Edward Mitchell (1993), Borland Pascal Developer's Guide, Section "Writing TSRs", pp. 370-400, with 778 lines of sample code. Also see example code files like ftp://garbo.uwasa.fi/pc/turboobj/tsrhelp.zip, ftp://garbo.uwasa.fi/pc/turbopa45/tess-5.zip, ftp://garbo.uwasa.fi/pc/turbopa45/tsrunit.zip, ftp://garbo.uwasa.fi/pc/turbopa7/pptsr10.zip, ftp://garbo.uwasa.fi/pc/turbopa7/tsrsrc35.zip, ftp://garbo.uwasa.fi/pc/turbopas/deltsr.zip, ftp://garbo.uwasa.fi/pc/turbopas/tp4_tsr.zip. Furthermore, you should see the TSR.SWG examples in the fine SWAG (SourceWare Archival Group's) collection of TP sources. Available from the /pc/turbopas directory at Garbo. For the current references to the SWAG files see ftp://garbo.uwasa.fi/pc/INDEX.ZIP. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:06 1996 Subject: Programming com ports 6. ***** Q: Why can't I read / write the com ports. A: Com port programming (most often writing telecommunication programs) is much much more complicated than simply trying to use write (com, whatever); read (com, whatever); This is a very advanced subject (frankly, beyond me), and the best way to learn is to try to obtain some code to show you how. One place to look at is Turbo Pascal text-books (I have a long list of them at garbo.uwasa.fi archives in my collection of Turbo Pascal units ftp://garbo.uwasa.fi/pc/ts/tsfaqp3470.zip. There also is an example by David Rind in ftp://garbo.uwasa.fi/pc/pd2/faquote.zip. Another source is International FidoNet pascal conference at some bulletin board near you. The conference has had some very good discussions in it. (No, I don't have them stored for distribution, nor any further information.) Some files you might wish to look at: ftp://garbo.uwasa.fi/pc/turbopas/comm_tp5.zip and comtty30.zip. Furthermore, you should see the COMM.SWG examples in the fine SWAG (SourceWare Archival Group's) collection of TP sources. Available from the /pc/turbopas directory at Garbo. For the current references to the SWAG files see ftp://garbo.uwasa.fi/pc/INDEX.ZIP. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:07 1996 Subject: Primers to interrupt programming 7. ***** Q: What are interrupts and how to use them in Turbo Pascal? A: An interrupt is a signal to the processor from a program, a hardware device, or the processor itself, to suspend temporarily what the program is doing, and to perform a routine that is stored in the operating system. There are 256 such interrupt routines, with many subservices stored in memory at locations, which are given in the so called interrupt table. Turbo Pascal (somewhat like C) has a special keyword Intr, and a predefined variable registers (in the Dos unit) to access these interrupt routines. One way of looking at them is as Turbo Pascal (complicated lowlevel) subroutines that are already there ready for you to call. A detailed description of interrupt routines is way beyond a single message with limited space. Instead, I shall give a simple example, and good references to the subject. (For a somewhat more comprehensive description of what an interrupt is, see INTERRUP.PRI in Ralf Brown's ftp://garbo.uwasa.fi/pc/programming/inter48b.zip.) : uses Dos; (* This procedure turns on the border color for CGA and VGA *) procedure BORDER (color : byte); var regs : registers; (* Predeclared in the Dos unit *) begin FillChar (regs, SizeOf(regs), 0); (* A precaution *) regs.ax := $0B00; (* Service number *) regs.bh := $00; (* Subservice number *) regs.bl := color; Intr ($10, regs); (* ROM BIOS video driver interrupt *) end; (* border *) If you are new the subject and / or want ideas on the most useful interrupts in Turbo Pascal programming, Ben Ezzel (1989), Programming the IBM User Interface Using Turbo Pascal, is definitely the best reference to look at. There are also many other good references for a novice interrupt user, such as Jamsa & Nameroff, Turbo Pascal Programmer's Library. If you are a more advanced interrupt user you'll find the following references very useful. Michael Tischer (1990), Turbo Pascal Internals; Norton & Wilton (1988), The New Peter Norton Programmer's guide to the IBM PC & PS/2; Ray Duncan (1988), Advanced MS-DOS Programming; Terry Dettmann (1989), Dos Programmer's Reference, Second edition, Que. Furthermore, there is an impressive list of interrupts collected and maintained by Ralf Brown. His extensive ftp://garbo.uwasa.fi:/pc/programming/inter48a.zip, inter48b.zip, inter48c.zip, inter48d.zip, inter48e.zip and inter48f.zip (or whatever are the current versions when you read this) is available by anonymous FTP or mail server from garbo.uwasa.fi. A definite must for an advanced user. Also see the reference to Brown's and Kyle's book in the bibliography at the end of this FAQ. There is also a good hypertext advanced programmer's quick reference ftp://garbo.uwasa.fi/pc/programming/helppc21.zip which you might find useful. One more point for Turbo Pascal users. When Borland upgraded from version 3 to 4.0 quite a number of tasks that needed to be done using interrupts (such as getting the current time) were included as normal TP routines. This means that while definitely useful, interrupt programming is now relevant only in advanced Turbo Pascal programming. Turbo Pascal 5.0 introduced a few more, but you can find some of the missing TP 4.0 routines in the compatibility unit in my ftp://garbo.uwasa.fi/pc/ts/tspa3440.zip TP units collection. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:08 1996 Subject: Borland's Turbo Pascal upgrades 8. ***** Q: Should I upgrade my Turbo Pascal version? A1: Depends on what version you are using, and for what purposes. If you are using version 3, the answer is a definite yes. There are so many useful additions in the later version, including the concept of units, and a great number of new useful keywords. The only reason that I can think of for using TP 3 is that it makes .com files (which reside in one memory segment only) instead of .exe files. As an accounting and business finance teacher and researcher I've been somewhat surprised to see postings stating that some users still have to program in TP 3.0 because their employer doesn't want to take the cost of upgrading. I find this cost argument ridiculous. How about some consideration for cost effectiveness and productivity? If you are currently using version 4.0, the most important point in considering upgrading is the integrated debugger in the later versions. It is really good, and useful if you write much code. There are some minor considerations, as well. Later versions contain some useful routines which 4.0 does not. I have programmed many of them to be available also for 4.0 in my units collection ftp://garbo.uwasa.fi/pc/ts/tspa3440.zip (or whatever is the latest when you read this). Furthermore, I find somewhat annoying that the executables will always end up in the default directory. If you are currently using version 5.0 the rational reasons for upgrading are needing objects, and a better overlay manager. I have also version 5.5 myself, but switched back to version 5.0 after I had some problems with its linking of object files. (This is a false statement from me, since it turned out that I had made a mistake myself. My thanks are due to bj_stedm@gould2.bristol-poly.ac.uk (Bruce Stedman) for questioning this item). Anyway, I don't use nor need OOP objects (don't confuse linking object files and object oriented programming here). One further point for 5.5. It has a better help function than 5.0, and a few more procedures and predefined constants. The TP 5.5 help includes examples, which can be even pasted into your program. This is handy. The real snag in upgrading (waiving the reasonable cost) is the fact that the units of the different versions are incompatible. If you have a large library of units (as I do) you will have to recompile the lot. This is something that has caused a fair amount of justifiable flak against an otherwise excellent product. A tip. Don't throw away your Turbo Pascal version 3.0 manual, if you have one. It is of use if you resort to the Turbo3 and Graph3 compatibility units. They give you access e.g. to turtle graphics. At the time of first writing this Turbo Pascal 6.0 version had just been announced. I didn't have it yet myself, but I had been (correctly) informed that its units are not compatible with the earlier versions. I now have Turbo Pascal 6.0, and I must say that my reactions have been disappointment and frustration. This is probably partly (but not entirely) my own fault, since Turbo Pascal seems to be headed from a common programming language into a full professional's specialized tool, with many features I don't know how to utilize. The only advancement from my point of view really is the multiple file editing, but I have long had alternative programs for that. If I used assembler (I don't) I am sure that I would find useful TP 6.0's potential to include assembler code as such instead of having to use the cumbersome Inline procedure of entering the assembler code. There is also a Windows Turbo Pascal, as the latest addition to the plethora. Since I don't use Windows at all, I have no further information on it. I think a pattern is emerging here. Rather than being different versions of the same product, the consecutive Turbo Pascals are really different products for different purposes. Version 3.0 was a simple programming language. Version 4.0 extended it into a full scale programming modular platform. Version 5.0 introduced the debugger. And there an advanced hobbyist's path ended. Version 5.5 introduced object oriented programming, which I'm sure is important for the initiated, but personally I just don't need it even if I write a lot of programs. And with the 6.0 we go completely out of the realm of conventional programming into Turbo Pascal visions. And Windows Turbo Pascal is for a different platform, altogether. I find the new integrated user interface of TP 6.0 awkward in comparison to what was used in the 4.0, 5.0, and 5.5 versions. The IDE of TP leaves less free memory than the previous versions. Furthermore TP 6.0 IDE performs frequent disk accesses which cause slowdowns making it virtually unusable from a floppy. And I wonder why Borland didn't at once go all the way to Windows, because that is what 6.0 really is. An intermediate, incomplete step in that direction. This means that we have a 5th upgrade in line with incompatible units. This is aggravating even for a TP fan, isn't it? For information on Turbo Pascal version 7.0 and Borland email contact numbers see ftp://garbo.uwasa.fi/pc/turbopa7/bp7-info.zip. Also see ftp://garbo.uwasa.fi/pc/turbspec/bp7bugs2.zip by Duncan Murdoch. Turbo Pascal 7.0 or more extensively Borland Pascal 7.0 is a full professional's tool, and far beyond for example my moderate programming needs. To list only a few of the features are protected mode programming, handling of large programs, very fast compiling, and a daunting amount of material elbowing its away on one's disk space if one ever has the patience to look through it all. I would use the word "overwhelming". But for a serious programmer this is an impressive and a very worthwhile tool. One should not be misled skipping it because of my comments which were written from a hobbyist's point of view. As a general trend in programs, the well-known columnist John C. Dvorak calls this increasing product complexity trend "featurism" in PC Computing, May 1993. But TP 7.0 (7.01) has some important features also from a hobbyist's point of view. So much so that I have finally succumbed to 7.01 myself. I particularly like the color coding of the keywords, and the TPX version enabling compiling very large programs (utilizing extended memory). A very welcome addition are the new keywords break and continue to exit or recycle a for, while or a repeat loop are. Besides they make using the outcast goto statements virtually unnecessary. A2: From: dmurdoch@watstat.waterloo.edu (Duncan Murdoch), Newsgroups: comp.lang.pascal. Included with Duncan's kind permission. [Duncan is one of the most knowledgeable and useful contributors to the comp.lang.pascal UseNet newsgroup (later replaced by comp.lang.pascal.borland for TP)]. One other reason: there's a bug in the code generator for 4.0 and 5.0 that makes it handle the Extended (10 byte) real type poorly. The code generated makes very poor use of the 8 element internal stack on the coprocessor, so that expressions with lots of operands like e1+e2+e3+e4+e5+e6+e7+e8+e9 always fail, if all the e's are of type extended. (The generated code pushes each operand onto the stack, then does all the adds. It's smarter to push and add them one at a time.) This makes it a real pain translating numerical routines from Fortran, especially since constants are taken to be of type extended. The bug was fixed in 5.5. A3: From: Bengt Oehman (d92bo@efd.lth.se): A difference between v4.0 and v5.5 is that you can calculate constants in tp55, but not in 4.0. I see this as a big advantage. For example: CONST MaxW = 10; MaxH = 20; MaxSize = MaxW*MaxH; { or } MaxX = 100; HalfMaxX = MaxX DIV 2; cannot be compiled with 4.0. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:09 1996 Subject: Shelling from a TP program 9. ***** Q: How do I execute an MS-DOS command from within a TP program? A: The best way to answer this question is to give an example. {$M 2048, 0, 0} (* <-- Important *) program outside; uses dos; begin write ('Directory call from within TP by Timo Salmi'); SwapVectors; Exec (GetEnv('comspec'), '/c dir *.*'); (* Execution *) SwapVectors; (* Testing for errors is recommended *) if DosError <> 0 then writeln ('Dos error number ', DosError) else writeln ('Mission accomplished, exit code ', DosExitCode); (* For DosError and DosExitCode details see the TP manual *) end. Alternatively, take a look at execdemo.pas from demos.arc which should be on the disk accompanying Turbo Pascal. What the above Exec does is that it executes the command processor. The /c specifies that the command interpreter is to perform the command, and then stop (not halt). I have also seen it asked how one can swap the Turbo Pascal program to the disk when shelling. It is unnecessary to program that separately because there is an excellent program to do that for you. It is ftp://garbo.uwasa.fi/pc/sysutil/shrom24b.zip. Somewhat surprisingly some users have had difficulties with redirecting shelled output. It is straight-forward. In the above code one would use, for example Exec (GetEnv('comspec'), '/c dir *.* > tmp'); -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:10 1996 Subject: Millisecond timing 10. ***** Q: How is millisecond timing done? A: A difficult task, but the facilities are readily available. TurboPower Software's commercial Turbo Professional (don't confuse with Borland's Turbo Professional) has a unit for this. (The usual disclaimer applies). It is called tptimer and is part of the ftp://garbo.uwasa.fi/pc/turbopas/bonus507.zip package. This one has been released to the PD. I have also seen a SimTel upload announcement of a ztimer11.zip for C and ASM, but I have no further information on that. ftp://garbo.uwasa.fi/pc/turbopas/qwktimer.zip is another option. It is not quite as accurate as tptimer. To test the tptimer unit in bonus507.zip you can use the following example code uses Crt, tptimer; var time1, time2 : longint; begin InitializeTimer; time1 := ReadTimer; Delay (1356); (* Or whatever code you wish to benchmark *) time2 := ReadTimer; RestoreTimer; writeln ('Elapsed = ', ElapsedTime (time1, time2)/1000.0 : 0 : 3); end. It is quite another question when you really need the millisecond timing. The most common purpose for millisecond timing is testing the efficiency of alternative procedures and functions, right? The way I compare mine is simple. I call the procedures or functions I want to compare for speed, say, a thousand times in a loop. And test this for elapsed time. This way the normal resolution (18.2 cycles per second) of the system clock becomes sufficient. This is accurate enough for the comparisons. var elapsed : real; i : word; elapsed := TIMERFN; (* e.g. from /pc/ts/tspa3455.zip *) for i := 1 to 1000 do YOURTEST; (* Try out the alternatives *) elapsed := TIMERFN - elapsed; writeln ('Elapsed : ', elapsed : 0 : 2); Incidentally, if you want to make more elaborate evaluations of the efficiency of your code, Borland's Turbo Profiler is a useful tool. (The usual disclaimer naturally applies.) -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:11 1996 Subject: Text font customizing 11. ***** Q: How can I customize the text characters to my own liking? A: As far as I know, text-mode characters are hard-coded, and cannot be customized at all unless you have an EGA or VGA adapter. But you can always retrieve the bitmap information for the ascii characters from your PC. The bitmap table for the lower part of the character set (0-127) starts at memory position $F000 and ends at $FA6E. The upper part is not at a fixed memory location. The pointer to the memory address of upper part of the ascii table (provided that graftabl has been loaded) is at an address $007C. One way of saying this is that the segment address of the upper part's memory location is at $007E, and its offset at $007C. Going into more details is beyond the scope of this posting. If you want more information see Michael Tischer (1992), PC Intern System Programming, "Selecting and Programming Fonts", pp. 197-210. It also has information on a remotely related task of using sprites (pp. 305-373), a concept familiar from the days of the Commodore 64 games programming. For another reference to customizing characters see Kent Porter (1987), Stretching Turbo Pascal, Chapter 12, and Kent Porter & Mike Floyd (1990), Stretching Turbo Pascal. Version 5.5. Revised Edition. Brady, Chapter 11. If you are interested in a demonstration of utilizing the bitmapped character information (no source code available), take a look at the demo in the garbo.uwasa.fi anonymous FTP archives file ftp://garbo.uwasa.fi/pc/ts/tsdemo16.zip (or whatever version number is current). Turbo Pascal also supports what is called stroked fonts (the .chr) files which draw characters instead of bitmapping them. The user should be able to write one's own .chr definitions, but I have no experience nor information on how this can be done. There is something called bgikit10.zip which has facilities for making fonts and adding graphics drivers. The problem is that I cannot make it publicly available, since I think that it is not PD. I am still missing the information. Unfortunately, it is not even the only case where I encountered the fact that Borland does not seem at all interested in the UseNet users' queries about the status and distributability of their material. (From Leonard Erickson Leonard.Erickson@f51.n105.z1.fidonet.org Well, you can *also* do it if you have a Hercules Graphics Card Plus or Hercules InColor card (as far as I know, the only cards that implemented Hercules RamFont 'standard'). And you can modify the upper 128 characters on a CGA card. BTW, the RamFont cards are *nice* pity it appeared too late to become a standard. It's a *lot* more flexible than EGA/VGA fonts (I can have several *dozen* fonts resident).) -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:12 1996 Subject: Finding files in TP 12. ***** Q: How to find the files in a directory AND subdirectories? A: Writing a program that goes through the files of the directory, and all the subdirectories below it, is based on Turbo Pascal's file finding commands and recursion. This is universal whether you are writing, for example, a directory listing program, or a program that deletes, say, all the .bak files, or some other similar task. To find (for listing or other purposes) the files in a directory you need above all the FindFirst and FindNext keywords, and testing the predefined file attributes. You make these a procedure, and call it recursively. If you want good examples with source code, please see PC World, April 1989, p. 154; Kent Porter & Mike Floyd (1990), Stretching Turbo Pascal. Version 5.5. Revised Edition, Chapter 23; Michael Yester (1989), Using Turbo Pascal, p. 437; Michael Tischer (1992), PC Intern System Programming, pp. 796-798. The simple (non-recursive) example listing all the read-only files in the current directory shows the rudiments of the principle of Using FindFirst, FindNext, and the file attributes, because some users find it hard to use these keywords. Also see the code in the item "How to establish if a name refers to a directory or not?" of this same FAQ collection you are now reading. uses Dos; var FileInfo : SearchRec; begin FindFirst ('*.*', AnyFile, FileInfo); while DosError = 0 do begin if (FileInfo.Attr and ReadOnly) > 0 then writeln (FileInfo.Name); FindNext (FileInfo); end; end. (* test *) A2: While we are on the subject related to FindFirst and FindNext, here are two useful examples: (* Number of files in a directory (not counting directories) *) function DFILESFN (dirName : string) : word; var nberOfFiles : word; FileInfo : searchRec; begin if dirName[Length(dirName)] <> '\' then dirName := dirName + '\'; dirName := dirName + '*.*'; {} nberOfFiles := 0; FindFirst (dirName, AnyFile, FileInfo); while DosError = 0 do begin if ((FileInfo.Attr and VolumeId) = 0) then if (FileInfo.Attr and Directory) = 0 then Inc (nberOfFiles); FindNext (FileInfo); end; {while} dfilesfn := nberOfFiles; end; (* dfilesfn *) (* Number of immediate subdirectories in a directory *) function DDIRSFN (dirName : string) : word; var nberOfDirs : word; FileInfo : searchRec; begin if dirName[Length(dirName)] <> '\' then dirName := dirName + '\'; dirName := dirName + '*.*'; {} nberOfDirs:= 0; FindFirst (dirName, AnyFile, FileInfo); while DosError = 0 do begin if ((FileInfo.Attr and VolumeId) = 0) then if (FileInfo.Attr and Directory) > 0 then if (FileInfo.Name <> '.') and (FileInfo.Name <> '..') then Inc (nberOfDirs); FindNext (FileInfo); end; {while} ddirsfn := nberOfDirs; end; (* ddirsfn *) -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:13 1996 Subject: A generic power function code for TP 13. ***** Q: I need a power function but there is none in Turbo Pascal. A: Pascals do not have an inbuilt power function. You have to write one yourself. The common, but non-general method is defining function POWERFN (number, exponent : real) : real; begin powerfn := Exp(exponent*Ln(number)); end; To make it general use: (* Generalized power function by Prof. Timo Salmi *) function GENPOWFN (number, exponent : real) : real; begin if (exponent = 0.0) then genpowfn := 1.0 else if number = 0.0 then genpowfn := 0.0 else if abs(exponent*Ln(abs(number))) > 87.498 then begin writeln ('Overflow in GENPOWFN expression'); halt; end else if number > 0.0 then genpowfn := Exp(exponent*Ln(number)) else if (number < 0.0) and (Frac(exponent) = 0.0) then if Odd(Round(exponent)) then genpowfn := -GENPOWFN (-number, exponent) else genpowfn := GENPOWFN (-number, exponent) else begin writeln ('Invalid GENPOWFN expression'); halt; end; end; (* genpowfn *) On the lighter side of things an extract from an answer of mine in the late comp.lang.pascal UseNet newsgroup: >anyone point out why X**Y is not allowed in Turbo Pascal? The situation in TP is a left-over from standard Pascal. You'll recall that Pascal was originally devised for teaching programming, not for something as silly and frivolous as actually writing programs. :-) -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:14 1996 Subject: Arrays > 64K 14. ***** Q: How can I create arrays that are larger than 64 kilobytes? A: Turbo Pascal does not directly support the so-called huge arrays but you can get by this problem with a clever use of pointers as presented in Kent Porter, "Handling Huge Arrays", Dr.Dobb's Journal, March 1988. In this method you point to an element of a two dimensional array using a^[row].col^[column]. The idea involves too much code and explanation to be repeated here, so you'll have to see the original reference. But I know from my own experience, that the code works like magic. (The code is available from garbo.uwasa.fi archives as ftp://garbo.uwasa.fi/pc/turbopas/ddj8803.zip). Kent Porter, "Huge Arrays Revisited", Dr.Dobb's Journal, October 1988, presents the extension of the idea to huge virtual arrays. (Virtual arrays mean arrays that utilize disk space). Another possibility is using TurboPower Software's (the usual disclaimer applies) commercial Turbo Professional (don't confuse with Borland's Turbo Professional) package. It has facilities for huge arrays, but they involve much more overhead than Kent Porter's excellent method. (* ================================================================= My code below is based on a UseNet posting in the late comp.lang.pascal by Naji Mouawad nmouawad@watmath.waterloo.edu. Naji's idea was for a vector, my adaptation is for a two-dimensional matrix. The realization of the idea is simpler than the one presented by Kent Porter in Dr.Dobb's Journal, March 1988. (Is something wrong, this is experimental.) ================================================================= *) {} const maxm = 150; maxn = 250; {} type BigVectorType = array [1..maxn] of real; BigMatrixType = array [1..maxm] of ^BigVectorType; {} var BigAPtr : BigMatrixType; {} (* Create the dynamic variables *) procedure MAKEBIG; var i : word; heapNeeded : longint; begin heapNeeded := maxm * maxn * SizeOf(real) + maxm * 4 + 8196; if (MaxAvail <= heapNeeded) then begin writeln ('Out of memory'); halt; end; for i := 1 to maxm do New (BigAPtr[i]); end; (* makebig *) {} (* Test that it works *) procedure TEST; var i, j : word; begin for i := 1 to maxm do for j := 1 to maxn do BigAPtr[i]^[j] := i * j; {} writeln (BigAPtr[5]^[7] : 0:0); writeln (BigAPtr[maxm]^[maxn] : 0:0); end; (* test *) {} (* The main program *) begin writeln ('Big arrays test by Prof. Timo Salmi, Vaasa, Finland'); writeln; MAKEBIG; TEST; end. (For a better test of the heap than in MAKEBIG see Swan (1989), pp. 462-463.) -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:15 1996 Subject: Testing printer status 15. ***** Q: How can I test that the printer is ready? A: Strictly speaking there is no guaranteed way to detect the printer status on a PC. As Brian Key Brian@fantasia.demon.co.uk wrote "Any book dealing with the PC BIOS support of a printer will quickly show you that there is no hardware definition which deals with the printer power status. It simply wasn't designed (and I use the word loosely!) into the IBM hardware specs." The usually advocated method in Turbo Pascal is to test the status of regs.ah after a call to interrupt 17 Hex (the parallel port driver interrupt), service 02: regs.dx := PrinterNumber; (* LPT1 = 0 *) regs.ah := $02; (* var regs : registers, uses DOS *) Intr ($17,regs); (* Interrupt 17 Hex *) status := regs.ah (* var status : byte *) But this is not a good method since the combinations of the status bits which indicate a ready state can vary from printer to printer and PC to PC. If you want a list of the status bits, see e.g. Ray Duncan (1988), Advanced MS-DOS Programming, p. 587. For an example of a code using interrupt 17 Hex see Douglas Stivison (1986), Turbo Pascal Library, pp. 118-120. Also see Michael Yester (1989), Using Turbo Pascal, pp. 494-495. The more generic alternative is to try to write a #13 to the printer having the i/o checking off, that is, while {$I-} is in effect, and testing the IOResult. But then you must first alter the printer retry times default (and restore it afterwards). Else the method can take up to a minute instead of an immediate response. Also, you must have set the FileMode for LPT1 appropriately (and restore it afterwards). Sounds a bit complicated, but you don't have to do all this yourself. There is a boolean function "LPTONLFN Get the online status of the first parallel printer" for this purpose in my ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip (or whatever version number is the latest) Turbo Pascal units collection available by anonymous FTP or mail server from garbo.uwasa.fi. A2: One potential, somewhat advanced solution is to use the Device Driver Control (IOCTL) information. Here is the code. uses Dos; function PRNSTAFN : boolean; var regs : registers; handle : ^word; f : file; begin prnstafn := false; if swap(Dosversion) < $0200 then exit; { At least MS-DOS 2.0 } Assign (f, 'prn'); { Printer } Reset (f); FillChar (regs, SizeOf(regs), 0); { Just to make sure } regs.ah := $44; { Function $44 } regs.al := $07; { Subfunction $07 } handle := @f; { Establish a file handle } regs.bx := handle^; Msdos (regs); { Call interrupt $21 } Close (f); if regs.flags and FCarry <> 0 then exit; { Is the carry flag set? } if regs.al <> $FF then exit; { regs.al = $FF signals success} prnstafn := true; end; (* prnstafn *) {} begin if PRNSTAFN then writeln ('Printer ready') else writeln ('Printer not ready'); readln; end. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:16 1996 Subject: Clearing the keyboard buffer 16. ***** Q: How can I clear the keyboard type-ahead buffer? A: Three methods are usually suggested for solving this problem. a) The first is to use something like uses Crt; while KeyPressed do ReadKey; This kludge-type method has the disadvantage of requiring the Crt unit. Also, in connection with procedures relying on ReadKey for input, it may cause havoc on the programs logic. b) The second method accesses directly the circular keyboard buffer var head : word absolute $0040:$001A; tail : word absolute $0040:$001C; procedure FLUSHKB; begin head := tail; end; For a slightly different formulation of the same method see Michael Tischer (1992), PC Intern System Programming, p. 462. c) The third method is to call interrupt 21Hex (the MS-DOS interrupt) with the ax register set as $0C00. This method has the advantage of not being "hard-coded" like the second method, and thus should be less prone to incompatibility. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:17 1996 Subject: Utilizing expanded memory 17. ***** Q: How can I utilize expanded memory (EMS) in my programs? A: I have no experience (yet?) on this subject myself, but I can give you a list of references: Michael Tischer (1990), Turbo Pascal Internals, Abacus, Chapter 9; Michael Tischer (1992), PC Intern System Programming, Chapter 12; Stephen O'Brien (1988), Turbo Pascal, Advanced Programmer's Guide, Borland-Osborne, Chapter 4; Chris Ohlsen & Gary Stoker (1989), Turbo Pascal Advanced Techniques, Que, Chapter 11, Robert Jourdain (1992), Programmer's Problem Solver, 2nd ed., Brady Publishing, pp. 68-87, and, maybe most importantly, Dorfman & Neuberger, Turbo Pascal Memory Management Techniques (with lots of code). Furthermore, Turbo Pascal delivery disks (at least 5.0) contain a demos.arc archive which includes an ems.pas file. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:18 1996 Subject: Capturing the entire command line 18. ***** Q: How can I obtain the entire command line (spaces and all)? A: ParamCount and ParamStr are for parsed parts of the command line and cannot be used to get the command line exactly as it was. See what happens if you try to capture "Hello. I'm here" you'll end up with a false number of blanks. For obtaining the command line unaltered use type CommandLineType = string[127]; var CommandLinePtr : ^CommandLineType; begin CommandLinePtr := Ptr(PrefixSeg, $80); writeln (CommandLinePtr^); end; A warning. If you want to get this correct (the same goes for TP's own ParamStr and ParamCount) apply them early in your program. At least they must be used before any disk I/O takes place! : A related example demonstrating a function giving the number of characters on the command line function CMDNBRFN : byte; var paramPtr : ^byte; begin paramPtr := Ptr (PrefixSeg, $80); cmdnbrfn := paramPtr^ end; (* cmdnbrfn *) For the contents of the Program Segment Prefix (PSP) see Tischer, Michael (1992), PC Intern System Programming, p. 753. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:19 1996 Subject: Redirecting from printer to file 19. ***** Q: How do I redirect text from printer to file in my TP program? A: Simple. This is done in Turbo Pascal by using the assign command (think what the word 'assign' implies). Here is a simple example of the idea. uses Printer; begin assign (lst, 'printer.log'); rewrite (lst); writeln (lst, 'Hello world'); close (lst); end. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:20 1996 Subject: Turbo Pascal users are just wimps 20. ***** Q: Turbo Pascal is for wimps. Why don't you use standard Pascal or better still why don't you use C? A: These kinds of "real-programmers" statements often reflect what is called self-over-others attitude, and they are a part of a kind of a programming lore or cult. Basically, these attitudes waive the simple fact that one should select one's tools according to the task at hand, not vice versa. On the other hand one's productivity is usually best when being able to use tools which one is familiar and comfortable with. (Note however that the real-programmer's lore is not really interested in producing results.) In very rough terms there are two attitudes to programming languages. They can be seen as tools for writing applications, or (by surprisingly many) as ends themselves. If we first look at standard Pascal (versus Turbo Pascal), considering the language primary and its usage secondary is common. This results from the history of Pascal, since as we all know it was originally meant as a means for teaching programming concepts, not at all for writing applications. But because Pascal turned out to be useful also for writing applications, it has been extended for some operating systems, most notably MS-DOS (Turbo Pascal) and VAX/VMS (VAX Pascal). Both remedy a lot of flaws from the application programmer's point of view. Most importantly they have a true file I/O interface, and enhanced string handling. Turbo Pascal (the more generic of these two) clearly draws from the structure and ideas of advanced BASICs (and vice versa). While in standard Pascal the language is an end by itself, for Turbo Pascal the only relevant issue is its usefulness for writing applications. One problem that one encounters when moving away from standard Pascal is the problem of portability. This is a truly serious problem, since most often extensive rewriting is necessary from converting say Turbo Pascal to, say, Unix Pascal. I have taken Unix Pascal as the extreme example, since Unix Pascal is almost nothing but the standard Pascal having no useful file I/O. If one considers C, its best aspect from applications point of view is portability, and its strength for system programming. But it is not an easy language to learn. Proponents of C also often have the tendency discussed above, that is seeing the language as primary, and its utilization as secondary. Now why this tendency, not only for C, but in general? I've had the opportunity of writing programs starting from the late 1960's, and one observation I have made, and often propounded the view is that it is not writing code that is the really difficult part. What is really difficult it is coming up with good and original ideas for programs to write. I see applications as primary, and the tools as secondary. As to Turbo Pascal, I've written in many languages (including Cobol, Fortran, several Basics and Pascals, and command languages) and I like Turbo Pascal because it is one of the most convenient and flexible tools for writing the kind of applications that I usually write and distribute for the Public Domain. That is I use Turbo Pascal because I'm comfortable with it in writing applications, and have thus gathered a very useful modular library for it over the years, not because of any inherent value attached to Turbo Pascal per se. A2: Another, a somewhat resembling line is made up by the arguments about standards in Pascal which were recycled in the late comp.lang.pascal time after time. Very often they end up with purists vs pragmatists arguing about the true or imaginary viles of using GOTOs. I find all this somewhat futile, although I understand the academic nature of the background. As you'll recall, Pascal was first developed for academic teaching programming concepts, not for any practical programming. That came later, and the ensuing popularity of Pascal in practical applications must have come as a surprise way back then. I admit being biased in not sympathizing with Pascal standard stalwarts. I am far more interested in getting the job done than in defending a barren orthodoxy. Since Turbo Pascal version 7.0 introduced the "break" and "continue" keywords to handle jumps in loops, GOTOs are much easier to avoid without undue complications. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:21 1996 Subject: Turning off the cursor 21. ***** Q: How do I turn the cursor off? A: The usually advocated trick for turning the cursor off is to equate the lower and the upper scan line of the cursor as explained e.g. in Stephen O'Brien (1988), Turbo Pascal, Advanced Programmer's Guide. uses Dos; var regs : registers; begin regs.ax := $0100; (* Service $01 *) regs.cl := $20; (* Top scan line *) regs.ch := $20; (* Bottom scan line *) Intr ($10, regs); (* ROM BIOS video driver interrupt *) end; To turn the cursor back on this (and many other) sources suggest setting regs.ch and regs.cl as 12 and 13 for mono screen, and 6 and 7 for others. This is not a good solution since it is equipment dependent, and may thus produce unexpected results. Better to store the current scan line settings, and turn off the cursor bit. Below is the code from ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip (or whatever version number is the latest) available by anonymous FTP from garbo.uwasa.fi archives. The general idea is that regs.ch bit 5 toggles the cursor on / off state. Thus to set the cursor off, apply regs.ch := regs.ch or $20; (* $20 = 00100000 *) and to set it on, apply regs.ch := regs.ch and $DF; (* $DF = 11011111 *) (* From TSUNTE unit, which also has a CURSON procedure *) procedure CURSOFF; var regs : registers; begin FillChar (regs, SizeOf(regs), 0); (* Initialize, a precaution *) {... find out the current cursor size (regs.ch, regs.cl) ...} regs.ah := $03; regs.bh := $00; (* page 1, superfluous because of FillChar *) Intr ($10, regs); (* ROM BIOS video driver interrupt *) {... turn off the cursor without changing its size ...} regs.ah := $01; (* Below are bits 76543210 *) regs.ch := regs.ch or $20; (* Turn on bit 5; $20 = 00100000 *) Intr ($10, regs); end; (* cursoff *) A2: A comment from Leonard Erickson leonard@qiclab.scn.rain.com. Reprinted with permission. There's a *reason* those sources don't suggest storing the current scan line settings. On IBM Monochrome Display Adapters (MDA), Hercules Graphics Cards, and the various clones of both, the "read cursor start and end scan lines" function *always* returns the same values. And those values are almost never the actual settings. Most cards return 6 & 7. Some return 12 & 13. But they return these values even if the cursor has been set to something else. So you are *still* stuck with checking the hardware type if the screen is in mode 7. See the Interrupt list for details on this mess. A3: Another solution that has been suggested is putting the cursor outside the screen. But you can't do this with the Crt's GotoXY procedure, since it ignores off screen positions, as observed by Luiz Marques luiz.marques%mandic@ibase.org.br. You'll need to use video interrupt, that is $10, function $02. Fair enough, but somewhat complicated. Besides, how do you write on the screen if the cursor position is off it? A4: This snippet of disabling the cursor at hardware level was posted to comp.lang.pascal (now news:comp.lang.pascal.borland) by JAB@ib.rl.ac.uk. Corrections due to John Stockton. John also points out that this probably needs a VGA to work. procedure turn_off_cursor; var num : word; begin port[$03D4]:=$0A; num:=port[$03D5]; port[$03D4]:=$0A; port[$03D5]:=num or 32; end; {} procedure turn_on_cursor; var num : word; begin port[$03D4]:=$0A; num:=port[$03D5]; port[$03D4]:=$0A; port[$03D5]:=num and not 32; end; {} procedure toggle_cursor; var num : word; begin port[$03D4]:=$0A; num:=port[$03D5]; port[$03D4]:=$0A; port[$03D5]:=num xor 32; end; A5: (Not to be taken seriously). Simple, turn off your computer and the cursor stops showing :-). -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:22 1996 Subject: Finding the roots of a polynomial 22. ***** Q: How to find all roots of a polynomial? A: If you need the code, see Turbo Pascal Numerical Toolbox and/or Press & Flannery & Teukolsky & Vetterling (1986), Numerical Recipes, The Art of Scientific Computing, Cambridge University Press. The Numerical Recipes codes are available as /pc/turbopas/nrpas13.zip (big, 404k!). If you just need to solve such a task (without code available), get ftp://garbo.uwasa.fi/pc/ts/tsnum12.zip (or whatever version number is the latest) from garbo.uwasa.fi archives by anonymous FTP or mail server. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:23 1996 Subject: Pascal homework on the net 23. ***** Q: What is all this talk about "Pascal homework on the net"? A: This is one of the subjects that seems to pop up at regular intervals, cause some heated exchange for awhile, and then die down again leaving some users harboring warranted or unwarranted grudges. Some posters to comp.lang.pascal (later comp.lang.pascal.borland) have been very concerned of the possibility that the questions posed on the net are related to students' homework assignments. I don't have any unequivocal answers or a clear-cut stand on this question, just some comments. The most important task of a newsgroup like comp.lang.pascal.borland is the exchange of information between the users. If you think that what you are going to post is interesting and useful to the group, that should be your topmost criterion. If it is really a student that wants his/her work done on the net (how do we know anyway?) also consider the following fact. Being able to use a newsgroup amounts to having learned at least something about using computers, and that is something per se. Even if the student may short-sightedly not realize it, providing ALL the code for a student's homework is detrimental to the student, since it is she/he that foregoes understanding what he/she is doing. The group should not condone outright cheating. Being (partly) a teacher myself, I understand also this view. If a student is stuck with a problem in his/her code, I don't see any real harm in helping out, especially if the problem has general interest. Instructing is what teaching is about, anyway, isn't it? But, on the other hand, I must admit that I find a it rather flagrant if a posting asks for something of the kind "I have to complete my term assignment to write a function plotter by the end of this month. Send me the code, since I'm too busy with my other exams to write it myself" (a true quote). Finally, let's not jump to premature conclusions about anyone's questions. That's what most often triggers off a vicious circle of flaming. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:24 1996 Subject: Linking bgi drivers into executables 24. ***** Q: How can I link graphics drivers directly into my executable? A: This is a complicated, yet a very useful task, because then you won't need any separate graphics drivers (or fonts) to go separately along with your program. Unfortunately, Turbo Pascal documentation on this task is a bit confusing. 1) The very first step is to get the necessary files from the Turbo Pascal disks to your working directory. To start with, you'll need binobj.exe and all the .bgi files. 2) Run the following commands (best to place them in a batch, call it e.g. makeobj.bat): binobj cga.bgi cga CGADriverProc binobj egavga.bgi egavga EGAVGADriverProc binobj herc.bgi herc HercDriverProc binobj pc3270.bgi pc3270 PC3270DriverProc binobj att.bgi att ATTDriverProc rem binobj ibm8514.bgi 8514 IBM8514DriverProc 3) Get drivers.pas from the Turbo Pascal disk and compile it with Turbo Pascal. Now you have a drivers.tpu unit which contains all the graphics drivers. 4) Now you won't need the .bgi and the .obj files any more. You may delete them from your working directory. 5) Write your graphics program in the usual manner. But before putting your program in the graphics mode use the following procedure if you want to link e.g. the EGAVGA graphics driver directly into your executable. (Link just the driver(s) you'll need, since the drivers take up a lot of space.) uses Graph, Drivers; : procedure EGAVGA2EXE; begin if RegisterBGIdriver(@EGAVGADriverProc) < 0 then begin writeln ('EGA/VGA: ', GraphErrorMsg(GraphResult)); halt(1); end; end; (* egavga2exe *) : Linking the .bgi and .chr drivers is also covered in Swan (1989), Mastering Turbo Pascal 5.5 pp. 355-359 and Mitchell (1993), Borland Pascal Developer's Guide , pp. 221-229. If you have Turbo Pascal 7.0 its help function gives you an example code. One way of getting at it is the following. In the Turbo Pascal IDE (that is in the editor) type RegisterBGIdriver. Then place the cursor on it and press alt-F1 for help of that keyword. Press alt-F10 and select "Copy example". Press first then alt-F10 and select Paste. The example code is pasted within your program for you to study. Incidentally, although this is a slightly different matter, you can link any data material into your executable. See Stephen O'Brien, (1988), Turbo Pascal, Advanced Programmer's Guide, pp. 31 - 35 for more details. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:25 1996 Subject: Trapping runtime errors 25. ***** Q: How can I trap a runtime error? A: What you are probably asking for is a method writing a program termination routine of your own. To do this, you have to replace Turbo Pascal's ExitProc with your own customized exec procedure. Several Turbo Pascal text books show ho to do this. See e.g. Tom Swan (1989), Mastering Turbo Pascal 5.5, Third edition, Hayden Books, pp. 440-454; Michael Yester (1989), Using Turbo Pascal, Que, pp. 376-382; Stephen O'Brien (1988), Turbo Pascal, Advanced Programmer's Guide, pp. 28-30; Tom Rugg & Phil Feldman (1989), Turbo Pascal Programmer's Toolkit, Que, pp. 510-515. Here is an example var OldExitProcAddress : Pointer; x : real; {$F+} procedure MyExitProcedure; {$F-} begin if ErrorAddr <> nil then begin writeln ('Runtime error number ', ExitCode, ' has occurred'); writeln ('The error address in decimal is ', Seg(ErrorAddr^):5,':',Ofs(ErrorAddr^):5); writeln ('That''s all folks, bye bye'); ErrorAddr := nil; ExitCode := 0; end; {... Restore the pointer to the original exit procedure ...} ExitProc := OldExitProcAddress; end; (* MyExitProcedure *) (* Main *) begin OldExitProcAddress := ExitProc; ExitProc := @MyExitProcedure; x := 7.0; writeln (1.0/x); x := 0.0; writeln (1.0/x); {The trap} x := 7.0; writeln (4.0/x); {We won't get this far} end. : Actually, I utilize this idea in my /pc/ts/tspa3470.zip Turbo Pascal units collection, which includes a TSERR.TPU. If you put TSERR in your program's uses statement, all the run time errors will be given verbally besides the usual, cryptic error number. That's all there is to it. That is, the inclusion to the uses statement to your main program (if you have the program in several units) is all you have to do to enable this handy feature. : Hans.Siemons@f149.n512.z2.fidonet.org notes "This line: ExitProc := OldExitProcAddress; should IMHO never be placed at the end of your exit handler. If for one reason or another your own handler should cause a runtime error, it would go in an endless loop. If the first statement restores the exit chain, this can never happen. I do agree that is not very likely that your exit handler produces any runtime error, but it performs I/O, and since it is located in a FAQ, people are bound to use, and maybe extend it with more tricky stuff." -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:26 1996 Subject: Using ansi codes in a TP program 26. ***** Q: How to get ansi control codes working in Turbo Pascal writes? A: It is very simple, but one has to be aware of the pitfalls. Let's start from the assumption that ansi.sys or a corresponding driver has been loaded, and that you know ansi codes. If you don't, you'll find that information in the standard MS-DOS manual. To apply ansi codes you just include the ansi codes in your write statements. For example the following first clears the screen and then puts the text at location 10,10: write (#27, '[2J'); (* the ascii code for ESC is 27 *) write (#27, '[10;10HUsing ansi codes can be fun'); If you want to test (as you should) whether ansi.sys or some some replacement driver has been loaded, you can use the ISANSIFN function from my ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip. Now the catches. If you have a uses Crt; statement in your program, direct screen writes will be used, and the ansi codes won't work. You have either to leave out the Crt unit, or include assign (output, ''); rewrite (output); : close (output); Occasionally I have seen it suggested that one should just set DirectVideo := false; This is a popular misconception. It won't produce the desired result. I'm not claiming to know the reason for this quirk of Turbo Pascal. Rather it is an observation I've made. -From: Bengt Oehman d92bo@efd.lth.se with a later dicussion with Bob Peck bpeck@prairienet.org and help from Duncan Murdoch dmurdoch@mast.queensu.ca. The `DirectVideo:=False' statement only tells the Crt unit to use BIOS calls instead of using direct video-memory writes. A demo program to illustrate the screen writing modes follows: Program ScreenWriteDemo; USES Crt; BEGIN Writeln('This is written directly to the video memory'); DirectVideo:=False; Writeln('This is written via BIOS interrupt calls (int 10h)'); Assign(Output,''); Append(Output); Writeln('This is written via DOS calls (int 21h)'); END. A note: The latter could be also written as Writeln(Output, 'This is written via DOS calls (int 21h)'); since the writeln default is the standard output. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:27 1996 Subject: Writing an expression parser 27. ***** Q: How to evaluate a function given as a string to the program? A: To do this you have to have a routine for parsing and evaluating your expression. This is a complicated task requiring a clever use of recursion. You can find such code in Stephen O'Brien (1988), Turbo Pascal, The Complete Reference. Borland-Osborne/McGraw-Hill, Chapter 10. Another, simpler piece of code can be found in Michael Yester (1989), Using Turbo Pascal, Que, Chapter 5. I've also written such a function evaluation program myself, and much of it is based on the ideas in O'Brien with my own corrections and enhancements. The resulting program is available as fn.exe function evaluator in the ftp://garbo.uwasa.fi/pc/ts/tsfunc13.zip package (or whatever version number is the latest). Note however, that the source code is not included, nor available. Tips from Justin Lee (ossm1jl@rex.uokhsc.edu): 67666 Sep 22 03:00 ftp://garbo.uwasa.fi/pc/turboobj/parstp30.zip parstp30.zip Recursive expression TP7.0/BP/VB/C++ parser, R.Loewy An excellent parser is included with all the Turbo Pascal versions since TP4.0 as part of the MCALC or TCALC spreadsheet example program. See mcparse.pas or tcparse.pas. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:28 1996 Subject: Detecting redirection 28. ***** Q: How does one detect whether input (or output) is redirected? A: As we know input to a program can come from a file, from the console, or from a pipe or redirection. Examples of the latter are type text.dat | program program < text.dat A Turbo Pascal program can be made to detect the redirections using Interrupt 21Hex, function 44Hex, subfunction 00Hex. See PC Magazine April 16, 1991, p. 374 for the code, and Duncan (1988), Advanced MS-DOS Programming, pp. 412-413 for more information. Alternatively, you can utilize the preprogrammed routines PIPEDIFN Is the standard input from redirection PIPEDNFN Is the standard output redirected to nul PIPEDOFN Is the standard output redirected from my ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip units. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:29 1996 Subject: Setting the 43/50 line text mode 29. ***** Q: How does one set the 43/50 line text mode? A: Quite simple. Just apply TextMode (C80 + font8x8). Requires a "uses Crt;". First, however, you should test that you have a at least an EGA video adapter. (See DetectGraph in your TP manual). Also see TSUTLE.NWS in ftp://garbo.uwasa.fi/pc/ts/tsutle22.zip (or whichever version number is the current) for the non-standard wide text modes like 132x43. { An example } uses Crt; var InitialMode : integer; begin InitialMode := LastMode; TextMode (CO80 + Font8x8); TextColor (LightCyan); writeln ('Test1'); readln; {} TextMode (CO40); writeln ('Test2'); readln; {} TextMode (InitialMode); TextColor (Yellow); writeln ('Test3'); readln; end. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:30 1996 Subject: Assigning environment variable values 30. ***** Q: How can I assign a value to an environment variable in TP? A: For assigning a value to (a parent process's) environment value you have to access and manipulate the Program Segment Prefix and Memory Control Blocks. This is a rather complicated undertaking. A source code with an accompanying article by Trudy Neuhaus can be found in PC Magazine Volume 11 Number 1 pages 425-427. The budding TP programmers should note that the elementary trick of Exec (GetEnv('comspec'), '/c set key=whatever') will achieve only a transient result for the duration of the exec shell. When you exit the shell after this endeavor, the environment will be as it was. Here is about the why. When the above command is executed, MS-DOS makes a copy of the environment, and uses the copy. When the above shelling terminates, the copy of the environment is deleted, and the original is restored. Hence the above trick cannot be used to change the parent environment. If you don't want to try to go through this rather complicated task yourself, the routines "SETEVN Set a parent environment variable (variable=value)" "SETENVSH Set an environment variable for the duration of shelling" can be found in my TP TPU collection ftp://garbo.uwasa.fi/pc/ts/ tspa34*.zip (* = 40,50,55,60,70). No source code is included, nor available for tspa34. However, there is a TPENV section within ftp://garbo.uwasa.fi/pc/turbopas/bonus507.zip. From zeta@tcscs.com Gregory Youngblood: For a source code see /pc/source/setenv.zoo at Garbo. One further detail. Users sometimes ask how one can change the prompt or the path from within a Turbo Pascal program. This is in no way different from changing the value of any other environment variable. Both PATH and PROMPT are environment variables that can be set with the MS-DOS SET command in the fashion described in the above. This is not changed in any way by the fact that you can apply PROMPT and PATH also in an alternative format not requiring the SET command. -------------------------------------------------------------------- -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:31 1996 Subject: Saving the screen 31. ***** Q: How does one store, and then restore the original screen? A: Here is a simple outline for storing and restoring a text mode screen in the standard 80 x 25 mode. Note that the code below is incomplete in a sense that it works for a color monitor only, because the monochrome screen address is $B000:$0000. For storing and restoring the graphics screen see Ohlsen & Stoker (1989), Turbo Pascal Advanced Techniques, Que, pp 333-337. uses Crt; type ScreenType = array [1..4000] of byte; (* 2 x 80 x 25 *) var ColorScreen : ScreenType Absolute $B800:$0000; SavedScreen : ScreenType; posx, posy : byte; begin SavedScreen := ColorScreen; (* Save the screen *) posx := WhereX; posy := WhereY; (* Save the cursor position *) writeln ('A simple demo storing and restoring the color text screen'); writeln ('By Prof. Timo Salmi, ts@uwasa.fi'); writeln; write ('Press <-'''); readln; ColorScreen := SavedScreen; (* Restore the screen *) GotoXY(posx,posy); (* Go to the stored cursor position *) end. If you would prefer not using the Crt unit, you can apply WHEREXFN, WHEREYFN, and GOATXY from TSUNTG.TPU from my units collection ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip. If you wish to test for the monitor type, that is choose between $B800:$0000 and $B000:$0000 bases, you can use the following function to test for the monochrome adapter. function MONOFN : boolean; var regs : registers; begin FillChar (regs, SizeOf(regs), 0); regs.ah := $0F; Intr ($10, regs); monofn := (regs.al = 7); end; (* monofn *) -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:32 1996 Subject: Converting TPUs 32. ***** Q: How can I convert a TPU unit of one TP version to another? A: Forget it. In practical terms such a conversion is not on. The Turbo Pascal TPU units are strictly version dependent. If there were a working solution I assume we would have heard of it long since. The hacks that have been tried won't solve this dilemma. For all practical purposes you need the source code and the relevant compiler version. You may nevertheless wish to ascertain for which version a TPU unit has been compiled. This is very simple. Just look at the first four character of a TPU file. The codes are TPU0 for 4.0 TPU5 for 5.0 TPU6 for 5.5 TPU9 for 6.0 TPUQ for 7.0 real mode But don't go editing these. It will not get you anywhere. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:33 1996 Subject: Finding about runtime errors 33. ***** Q: Which error is e.g. Runtime error 205 A: Basically this is a case of RTFM (read the f*ing manual). But it is very easy to find out even without resorting to the manual. Put temporarily the statement RunError (205); as the first statement of your program. Then run your program from the Turbo Pascal IDE, that is from within the TP editor. The description of the error will appear. If you run a program from within a Turbo Pascal IDE, it is advisable to turn on the debug options on. You'll get both the error number and the description. Furthermore by pressing F1 after the error you get its description in a more verbal format. One further trick is to put "uses TSERR"; (Include verbal run-time error messages) into your program. If you do that, the run-time errors will be given with a verbal description not just as a number. TSERR.TPU is part of my TPU collection at Garbo ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip. In TP 7.0 the run time errors can also be found by invoking "Help" from the main manu (Alt-H) and selecting "Error messages". -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:34 1996 Subject: Opening read-only files 34. ***** Q: Why can't I open read-only files? I get "File access denied". A: The answer is rather simple, but it is not well displayed in the manuals. In order to read a read-only file you have to set the FileMode as 0 like below. Else you'll get runtime error 005 "File access denied". var f : text; (* Can be any file type *) savefm : byte; begin savefm := FileMode; (* Save the current FileMode status *) FileMode := 0; (* The default is 2 *) assign (f, 'readonly.txt'); reset (f); { have your wicked ways } close (f); FileMode := savefm; (* Restore the original FileMode *) end. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:35 1996 Subject: Getting a nybble from a byte 35. ***** Q: I have a variable of type BYTE and would like to extract two numbers from it. (The first 4 bits making up number A, the second 4 bits making up number B). How can I extract these two numbers? A: Ah, this question brings back the good bad old days of the Commodore C64 programming when bit operations were rather a rule than an exception. Here is the solution. function HIBYTEFN (x : byte) : byte; begin hibytefn := x Shr 4; (* Shift right by four bits *) end; {} function LOBYTEFN (x : byte) : byte; begin lobytefn := x and 15; (* x and 00001111 *) end; From Patrick Taylor (exuptr@exu.ericsson.se): Ah, leave it to Timo to come up with a different way! An other is (n div 16) (n mod 16). Patrick is right. But unless the compiler is optimized, the former produces more efficient code. Not that it really makes any practical difference whatsoever. Of course the fastest code is produced using assembler as pointed out by Maarten Pennings (maarten@cs.ruu.nl) who provided the following inline example: function high(b:byte):byte; inline($58 { POP AX | AH=?, AL=b } /$30/$e4 { XOR AH,AH | AH=0, AL=b } /$b9/$04/$00 { MOV CX,0004 | AH=0, AL=b, CL=4 } /$d3/$e8 { SHR AX,CL | AX=b shr 4 } ); A2: Getting a word from a longint can alternatively be achieved without any calculations by using a kind of typecasting. Below is the code I have utilized in ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip. (* Get the high-order word of the longint argument *) function HIWORDFN (x : longint) : word; type type1 = record low : word; high : word; end; var m1 : type1 absolute x; begin hiwordfn := m1.high; end; (* hiwordfn *) -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:36 1996 Subject: Setting hi-intensity background 36. ***** Q: How can I set a hi-intensity color background in the text mode? A: As you should know, the you can test for a blinking text for example as follows. uses Crt; begin TextColor (11 + 128); (* or LightCyan + Blink *) TextBackground (Blue); writeln ('What''s the catch?'); (* An aside, note the '' pair *) end. In the above, bit 7 (the 128) controls the blinking. If you have at least an EGA, you can alter the interpretation of the highest text color bit to denote a hi-intensity background, but then you lose the the blinking. The following piece of code disables blinking, enabling a hi-intensity background. uses Dos; var regs : registers; begin FillChar (regs, SizeOf(regs), 0); (* An initialization precaution *) regs.ah := $10; (* Function $10 *) regs.al := $03; (* Subfunction $03 *) regs.bl := $00; Intr ($10, regs); (* ROM BIOS video driver interrupt *) end. To enable blinking again, set regs.bl := $01; Any high-intensity background you may have currently on the screen, will instantly change into a blinking text a a low-intensity background. A2: The previous answer assumes at least an EGA. Otherwise ports must be accessed. This is both advanced and dangerous programming, because errors in handling posts can do real harm. Besides it is fair to require at least an EGA in writing modern programs, at least for non-laptops, and on the latter the colors don't really matter for CGA and below. Let's take a look, nevertheless, how this is done for a CGA. Note that this won't work an an EGA and beyond, not at least in my tests. For detecting the video adapter you have, see the DetectGraph procedure in you Turbo Pascal manual. First we need some basics from MEMORY.LST in Ralf Brown's ftp://garbo.uwasa.fi/pc/programming/inter48b.zip (or whatever version is current): Format of BIOS Data Segment at segment 40h: 63h WORD Video CRT controller base address: color=03D4h, mono=03B4h 65h BYTE Video current setting of mode select register 03D8h/03B8h From David Jurgens's ftp://garbo.uwasa.fi/pc/programming/helppc21.zip we see 3D0-3DF Color Graphics Monitor Adapter (ports 3D0-3DB are write only, see 6845) 3D8 6845 Mode control register (CGA, EGA, VGA, except PCjr) From Darryl Friesen's (friesend@jester.usask.ca) in the late comp.lang.pascal we have, the following procedure, with my own added comments (* *). procedure SetBlinkState (state : boolean); var ModeRegPort : word; ModeReg : byte; begin Inline($FA); { CLI } (* Interrupts off *) ModeRegPort := MemW[$0040:$0063]+4; (* Typically $03D4+4 = $03D8 *) ModeReg := Mem[$0040:$0065]; (* Typically 1001 *) if state then (* Bit 5 controls blink enable *) ModeReg := ModeReg or $20 (* $20 = 00100000 (base2) *) else ModeReg := ModeReg and $DF; (* $DF = 11011111 disable *) Port[ModeRegPort] := ModeReg; (* Typically $9 = 00001001 *) Mem[$0040:$0065] := ModeReg; (* or $29 = 00101001 *) Inline($FB) { STI } (* Interrupts on *) end; -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:37 1996 Subject: Pascal to C 37. ***** Q: Where can I find a program to convert (Turbo) Pascal to C? A: This is a relevant question, but I have placed elsewhere the tips on the "looking for a program" questions. Here are the pointers to further pointers :-). (The FAQ versions might have been updated since I wrote this.) ftp://garbo.uwasa.fi/pc/pd2/camfaq.zip camfaq.zip comp.archives.msdos.(d/announce) FAQ (general finding) : ftp://garbo.uwasa.fi/pc/ts/tsfaqn43.zip tsfaqn43.zip Questions from UseNet and Timo's answers : ftp://garbo.uwasa.fi/pc/pd2/faquote.zip faquote.zip Old information from tsfaq Frequently Asked Questions -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:38 1996 Subject: Turning off the input echo 38. ***** Q: How can I read input without echoing to the screen? A: It is fairly simple. Study this example source code, with the manual, if need be. uses Crt; var password : string; {} (* Read without echoing *) procedure GETPASS (var s : string); var key : integer; ch : char; begin s := ''; repeat ch := ReadKey; key := ord (ch); case key of 0 : ch := ReadKey; (* Discard two-character keys, like F1 *) 13 : exit; (* Enter has been pressed *) 1..12,13..31,255 :; (* Discard the special characters *) else s := s + ch; end; until false; end; (* getpass *) {} (* The main program *) begin write ('Password: '); GETPASS (password); writeln; writeln (password); end. {} If you wish to be able to edit the input stream, like having the BackSpace functional, that is more complicated, and is left as an exercise after these basics. A hint: 8 : Delete (s, Length(s), 1); There is another approach to this problem pointed out by Colin Lamond colin@sound.demon.co.uk. Quite innovative in its simplicity once one comes to think of it. "Set the textcolor, and the textbackground to the same color, and so the typed text can not be seen on the screen." -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:39 1996 Subject: Input line-editing 39. ***** Q: How can I edit the readln input stream? A: In practice, if you wish to use anything beyond simple the BackSpace deleting, you'll have to build your own line editing routines expanding on the code in the previous item. It is quite a task, and you can alternatively find the preprogrammed routines in my Turbo Pascal units ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip (or whatever version number is current). EDRDEBLN Editable Readln with ctrl-c, break trapping, pre-fill etc EDRDEFLN Editable Readln with recall, pre-fill, and insert toggle EDRDLN Readln with line-editing potential (the simplest) EDREABLN Edreadln with ctrl-c and break trapping EDREADLN Editable Readln with recall, and insert toggle -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:40 1996 Subject: Executable branding 40. ***** Q: How can I write (brand) something into my executables? Here is the actual question that led me to writing this item: 'I am very interested in the .EXE "branding" techniques you use in your TSUNTI unit. Would it be possible to get hold of the source code for that unit, as it would save me from having to re-invent the wheel?' A: What you are referring to is BRANDEXE Store information within your program's .exe file (MS-DOS 3.0+) CHKSUMFN Checksum self-test to detect any tampering (MS-DOS 3.0+) USECOUNT Get the number of times the program has been used Sorry no, I don't want to distribute my source codes from ftp://garbo.uwasa.fi/pc/turbopas/ts/tspa3470.zip. Besides they would be less useful to you than you may think because internally my programs are in Finnish, comments, variable and procedure names, and all. But I can hopefully help you by giving a reference to a similar code. Please see Ohlsen & Stoker, Turbo Pascal Advanced Techniques, Que, 1989, p. 420. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:41 1996 Subject: Elusive, inconsistent errors 41. ***** Q: What is wrong with my program? It hangs without a clear pattern? A: With experience one learns that some programming errors are very elusive. I have many times seen users declaring that they have found a bug in Turbo Pascal, but in the overwhelming majority of cases it still is just a programming error, which just is more difficult to find than the more clear-cut cases. When you have symptoms like your program crashing from within the IDE, but working seemingly all right when called as stand-alone, or something equally strange, you might have one of the following problems. - A variable or some variables in your code are uninitialized thus getting random values, which differ depending on your environment. - Your indexes are overflowing. Set on the range check {$R+} directive for testing. - An error in the pointer logic. Normal debugging does not necessarily help in locating these errors because one is easily led to debugging the wrong parts of one's program. Especially the latter two reasons can cause errors which seemingly have nothing to do with the actual cause. This results from the fact that indexing and pointer errors can overwrite parts of memory causing strange quirks in your program. If you have used indexing with {$R-} or if you use pointer operations, sooner or later you are bound to have these problems in developing your applications. See Edward Mitchell (1993), Borland Pascal Developer's Guide, 275-288 for common programming errors and especially the information on memory clobbering, in a useful chapter on debugging Turbo Pascal programs. You might also take a look at your Turbo Pascal User's Guide. At least version 7.0 has an instructive general categorization of errors on pages 76-77. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:42 1996 Subject: Converting the number base 42. ***** Q: How do I convert a decimal word into a hexadecimal string, etc? A: Here is one possibility function HEXFN (decimal : word) : string; const hexDigit : array [0..15] of char = '0123456789ABCDEF'; begin hexfn := hexDigit[(decimal shr 12)] + hexDigit[(decimal shr 8) and $0F] + hexDigit[(decimal shr 4) and $0F] + hexDigit[(decimal and $0F)]; end; (* hexfn *) Here is another conversion example (from longint to binary string) function LBINFN (decimal : longint) : string; const BinDigit : array [0..1] of char = '01'; var i : byte; binar : string; begin FillChar (binar, SizeOf(binar), ' '); binar[0] := chr(32); for i := 0 to 31 do binar[32-i] := BinDigit[(decimal shr i) and 1]; lbinfn := binar; end; (* lbinfn *) For a full set of conversions, both from and to decimal, apply TSUTNTB.TPU from ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:43 1996 Subject: Identifying the last drive 43. ***** Q: How to determine the last drive? A: One way of doing that is utilizing the information in DPB, that is the Drive Parameter Block, but that is rather complicated, so you can find that without source code in the TSUNTH unit in ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip . Another way is using interrupt 21H, function 36H to detect if a drive exists starting from the first drive letter. The code is given below. The disadvantage of this method is that it does not distinguish between real and substituted drives. uses Dos; function LASTDFN : char; (* Detect last harddisk letter *) var regs : registers; i : byte; begin i := 2; repeat Inc(i); FillChar (regs, SizeOf(regs), 0); regs.ah := $36; regs.dl := i; MsDos(regs); until (regs.ax = $FFFF); lastdfn := chr(i+63); end; (* lastdfn *) -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:44 1996 Subject: Clock display in a TP program 44. ***** Q: How can I put a running clock into my Turbo Pascal program? A: We are not speaking of a stand-alone TSR-clock (which is a different task), but considering a clock that continuously displays the time in some part of the output screen of your Turbo Pascal program. You might first want to read the earlier items about ReadKey usages if you are not familiar with it (you probably are, because you would not pose this advanced question if you were a novice). The items are the unlikely "How do I disable or capture the break key in Turbo Pascal?" and "How can I read input without echoing to the screen?" The general idea is to make the body of the program a repeat until loop using ReadKey for input and updating the clock display at suitable junctions within the loop. The scheme is thus something like the following. procedure showtime; begin { if the second has changed, write the time } end; : repeat { do whatever } showtime; if KeyPressed then case ReadKey of { whatever } { exit rules } end; showtime; : showtime; until false; One trick of the trade is that you must not update your clock each time the clock routine is encountered. You should test if the second has changed, and update only then. Else you are liable to get an annoying flicker in your clock. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:45 1996 Subject: Is a name a directory 45. ***** Q: How to establish if a name refers to a directory or not? A: This question has turned out a bit more complicated than I first thought. There are several methods, each with some catch. The first is trying to open the name as a file and observing the IOResult. The ISDIRFN function in ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip TPU unit TSUNTJ.TPU is based on this method. Unfortunately it is not always stable. I have been reported problems in connection with DRDOS by Richard Breuer (ricki@pool.informatik.rwth-aachen.de) who has tested these routines. The second method (ISDIR2FN) is based on the fact that the file NUL exists in a directory if the directory exists. The thrid method (ISDIR3FN) is a brute force method. It is given below, since it is quite an instructive little exercise of Turbo Pascal programming. (* Search recursively through a drive's directories. Auxiliary, recursive procedure for ISDIR3FN *) procedure SEARCHDR (Path, FileSpec : string; name : string; var found : boolean); var FileInfo : SearchRec; begin FindFirst (Path + '*.*', Directory, FileInfo); while DosError = 0 do begin if ((FileInfo.Attr and Directory) > 0) and (FileInfo.Name <> '.') and (FileInfo.Name <> '..') then begin SEARCHDR (Path + FileInfo.Name + '\', FileSpec, name, found); if Path + FileInfo.Name + '\' = name then found := true; end; FindNext (FileInfo); end; {while} end; (* searchdr *) (* Does a name refer to a directory *) function ISDIR3FN (name : string) : boolean; var drive : char; found : boolean; begin {... Default value ...} isdir3fn := false; {... Discard empty names ...} if name = '' then exit; {... Expand into a fully qualified name, makes it uppercase ...} name := FExpand (name); if name[Length(name)] <> '\' then name := name + '\'; {... Extract the drive letter from the name ...} drive := UpCase (name[1]); {... Check first for the root ...} if drive + ':\' = name then begin isdir3fn := true; exit; end; {... Check the rest of the directories recursively ...} found := false; SEARCHDR (drive + ':\', '*.*', name, found); isdir3fn := found; end; (* isdir3fn *) -Date: Mon, 13 Jun 1994 00:13:05 +0000 (GMT) -From: JEROEN SCHIPPER -To: ts@uwasa.fi (Timo Salmi) -Subject: Is a name a directory in TP The method I use is simply checking the attribute bit, as this small program will demonstrate: program isdir; uses dos; var s:string; attr:word; f:file; begin repeat readln(s); if s = '' then break; assign(f,s); getfattr(f,attr); if doserror <> 0 then writeln('DOS error code = ', doserror) else begin if attr and directory <> 0 then writeln(S,' is a directory') else writeln(S,' is a not directory') end; until false; end. The methods you mention in your faq are far more complicated, but why? Is there are catch why the method above won't work? I guess don't really understand the problem here. Jeroen. A2: This has turned out to be a tricky FAQ. There are some additional suggestions and comments from the gentle readers. You can track them from ftp://garbo.uwasa.fi/pc/ts/tspost00.zip index. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:46 1996 Subject: Disabling alt-ctrl-del 46. ***** Q: How does one disable alt-ctrl-del? A: I can only give a pointer to source code. Take a look at 4067 Jul 1 1993 ftp://garbo.uwasa.fi/pc/turbopa7/cadthf10.zip cadthf10.zip CadThief TP6+ unit for trapping ctrl+alt+del, M.Hanninen and 30673 Oct 13 1987 ftp://garbo.uwasa.fi/pc/turbopas/keyint.zip keyint.zip Disable alt-ctrl-del + other int09h TP tricks, N.Rubenking and 7105 Apr 19 10:51 ftp://garbo.uwasa.fi/pc/turbopa7/cad_int9.zip cad_int9.zip Disable Ctrl-Alt-Del via new TP kb interrupt, J.Robertson Also see Lou Duchez's source code in TSR.SWG examples in the fine SWAG (SourceWare Archival Group's) collection of TP sources. Available from the /pc/turbopas directory at Garbo. For the current references to the SWAG files see ftp://garbo.uwasa.fi/pc/INDEX.ZIP. I have utilized alt-ctrl-del disabling at least in one of my own programs (PESTIKID.EXE). The code is not available, but the general idea is replacing the old keyboard interrupt ($09) with a handler of one's own. If the handler detects alt-ctrl-del, the keyboard is reset, else the handler is chained back to the original interrupt. The chaining requires a rather complicated inline procedure provided in TurboPower Software's kit. An additional complication is that the del keypress must be intercepted already at the relevant port $60, and the alt and ctrl status must be tested, so that the rebooting will not be invoked. Resetting the keyboard requires accessing the $20 and $61 ports. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:47 1996 Subject: Does a file exist 47. ***** Q: How can I test whether a file exists? A: There are several alternatives. Here is the most common with example code. It recognizes also read-only, hidden and system files. function FILEXIST (name : string) : boolean; var fm : byte; f : file; b : boolean; begin fm := FileMode; FileMode := 0; assign (f, name); {$I-} reset(f); {$I+} b := IOResult = 0; if b then close(f); filexist := b; FileMode := fm; end; A second alternative is Uses Dos; function FILEXIST (name : string) : boolean; var f : file; a : word; begin assign (f, name); GetFAttr (f, a); filexist := false; if DosError = 0 then if ((a and Directory) = 0) and ((a and VolumeId) = 0) then filexist := true; end; A third alternative is Uses Dos; function FILEXIST (name : PathStr) : boolean; begin filexist := FSearch (name, '') <> ''; end; A fourth alternative is the following. Be careful with this option, since it works a bit differently from the others. It accepts wild cards. Thus, for example FILEXIST('c:\autoexec.*') would be TRUE in this method, while FALSE in all the above. Uses Dos; function FILEXIST (name : string) : boolean; var f : SearchRec; begin filexist := false; FindFirst (name, AnyFile, f); if DosError = 0 then if (f.attr <> Directory) and (f.attr <> VolumeId) then filexist := true; end; A good variation from KDT@newton.national-physical-lab.co.uk of this theme, disallowing wildcards: function file_exists (fname :string) :boolean; var f :searchrec; begin findfirst (fname, anyfile - directory - volumeid, f); file_exists := (doserror + pos('*',fname) + pos('?',fname) = 0); end; -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:48 1996 Subject: The current program name 48. ***** Q: What is the name of the current Turbo Pascal program? A: The name of the currently executing Turbo Pascal program is in ParamStr(0). This was introduced in TP version 5.0, and as far as I recall at least MS-DOS version 3.0 is required. For TP 4.0 you can use "ParamStr0 The name of the program" from TSUNT45 in ftp://garbo.uwasa.fi/pc/ts/tspa3440.zip (or whatever the version number is the latest). It is advisable to put the value into a string variable at be beginning of the program before eny I/O takes place. Thus you might wish to use: var progname : string; begin { the main program } progname := ParamStr(0); : A bonus of this method is that you can access the individual characters of progname (e.g. progname[1] for the drive) while that is not possible to do for the ParamStr keyword. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:49 1996 Subject: How can a program reboot my PC? 49. ***** Q: How is the code for rebooting the PC written in Turbo Pascal? A: This item draws from the information and the C-code example in Stan Brown's, later J.Carlyle's comp.os.msdos.programmer FAQ, ftp://garbo.uwasa.fi/pc/doc-net/dosfv204.zip (at the time of updating this), from memory.lst and interrup.b in ftp://garbo.uwasa.fi/pc/programming/inter48b.zip, and from ftp://garbo.uwasa.fi/pc/programming/helppc21.zip. The Turbo Pascal code is my adaptation of the C-code. It is not a one-to-one port. The usually advocated warm-boot method is storing $1234 in the word at $0040:$0072 and jumping to address $FFFF:$0000. The problem with this approach is that files must first be closed, potential caches flushed. This is how to do this procedure REBOOT; label next; var regs : registers; i : byte; ticks : longint; begin {... "press" alt-ctrl ...} mem[$0040:$0017] := mem[$0040:$0017] or $0C; { 00001100 } {... "press" del, try a few times ...} for i := 1 to 10 do begin FillChar (regs, sizeOf(regs), 0); { initialize } regs.ah := $4F; { service number } regs.al := $53; { del key's scan code } regs.flags := FCarry; { "sentinel for ignoring key" } Intr ($15, regs); {... check if the del key registered, if not retry ...} if regs.flags and Fcarry > 0 then goto next; {... waste some time, watch out for midnight ...} ticks := MemL [$0040:$006C]; repeat until (MemL[$0040:$006C] - ticks > 3) or (MemL[$0040:$006C] - ticks < 0) end; {for} exit; next: {... disk reset: writes all modified disk buffers to disk ...} FillChar (regs, sizeOf(regs), 0); regs.ah := $0D; MsDos (regs); {... set post-reset flag, use $0000 instead of $1234 for coldboot ...} memW[$0040:$0072] := $1234; {... jump to $FFFF:0000 BIOS reset ...} Inline($EA/$00/$00/$FF/$FF); end; (* reboot *) One slight problem with this approach is that the keyboard intercept interrupt $15 service $4F requires at least an AT according to ftp://garbo.uwasa.fi/pc/programming/inter48b.zip. A simple test based on "FFFF:E byte ROM machine id" (the previous definition is from ftp://garbo.uwasa.fi/pc/programming/helppc21.zip) is: function ISATFN : boolean; begin case Mem[$F000:$FFFE] of $FC, $FA, $F8 : isatfn := true; else isatfn := false; end; {case} end; (* isatfn *) For a more comprehensive test use CPUFN "Get the type of the processor chip" from TSUNTH in ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip or see the TP + ASM code in Michael Ticher (1992), PC Intern System Programming, pp. 725-727. An addition by Per Bergland (d6caps@dtek.chalmers.se): I recently downloaded the FAQ for this newsgroup, and studied the code for rebooting a PC. The problem with that code (calling FFFF:0000) is that it will not work in protected mode programs such as those compiled for Windows or BP7 DPMI, or even in a DOS program run in a Windows DOS session. The solution provided has been tested on various COMPAQ PC:s, but I think it will work on any AT-class machine. It involves using the 8042 keyboard controller chip output pin 0, which is physically connected to the reset pin of the CPU. There is unfortunately no way to perform a "warm" reboot this way, and the warnings about disk caches etc apply to this code, too (see FAQ). The code is written in BP7 assembly lingo, because that's what I normally write code in, but anyone could rewrite it in C or high level Pascal. UNIT Reboot; INTERFACE procedure DoReboot; IMPLEMENTATION procedure DoReboot;assembler; asm cli @@WaitOutReady: { Busy-wait until 8042 is ready for new command} in al,64h { read 8042 status byte} test al,00000010b { Bit 1 of status indicates input buffer full } jnz @@WaitOutReady mov al,0FEh { Pulse "reset" = 8042 pin 0 } out 64h,al { The PC will reboot now } end; END. -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 00:00:50 1996 Subject: Writing inline code 50. ***** Q: How can I write inline code? A: In Turbo Pascal versions prior 6.0 assembler code could not be directly included in the code. Instead one had to assemble the code into inline statements. Consider the task of rebooting the PC (without disk closing and cache flushing). The assembler code for this is mov ax,$40 mov ds,ax mov wo [$72],$1234 jmp $FFFF:$0000 To assemble this code into an inline statement write the following file calling it e.g. debug.in. The empty line is important. Also carefully note that debug assumes hexadecimal notation. Do not use the $ designator in debug.in. .... begin debug.in, cut here .... a 100 mov ax,40 mov ds,ax mov wo [72],1234 jmp FFFF:0000 u 100 q .... end debug.in, cut here .... Give the following command debug < debug.in You'll get 0E9E:0100 B84000 MOV AX,0040 0E9E:0103 8ED8 MOV DS,AX 0E9E:0105 C70672003412 MOV WORD PTR [0072],1234 0E9E:010B EA0000FFFF JMP FFFF:0000 This translates into Inline ($B8/$40/$00/ $8E/$D8/ $C7/$06/$72/$00/$34/$12/ $EA/$00/$00/$FF/$FF); A2: You can also utilize an inline <--> asm converter called ftp://garbo.uwasa.fi/pub/pc/turbopas/inlin219.zip inlin219.zip Inline assembler for Turbo Pascal, w/src, D.Baldwin It has two sources, inline.pas and uninline.pas which you can compile to do the conversions in both directions for you. For example, if you have a file test.asm containing the mov ax,$0040 mov ds,ax mov word ptr [$72],$1234 jmp far $FFFF:$0000 then "inline test.asm" will produce test.obj with the following, expected contents Inline( $B8/$40/$00/ {mov ax,$0040} $8E/$D8/ {mov ds,ax} $C7/$06/$72/$00/$34/$12/ {mov word ptr [$72],$1234} $EA/$00/$00/$FF/$FF); {jmp far $FFFF:$0000} -------------------------------------------------------------------- From ts@uwasa.fi Mon Jan 1 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 Mon Jan 1 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 Mon Jan 1 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 Mon Jan 1 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 Mon Jan 1 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 Mon Jan 1 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 Mon Jan 1 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 Mon Jan 1 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 Mon Jan 1 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:$001