From ts@uwasa.fi Thu Mar 7 00:00:00 1996 Subject: FAQPAS2.TXT contents Copyright (c) 1993-1996 by Timo Salmi All rights reserved FAQPAS2.TXT More frequently (and not so frequently) asked Turbo Pascal questions with Timo's answers. The items are in no particular order. You are free to quote brief passages from this file provided you clearly indicate the source with a proper acknowledgment. Comments and corrections are solicited. But if you wish to have individual Turbo Pascal consultation, please post your questions to a suitable Usenet newsgroup like news:comp.lang.pascal.borland. It is much more efficient than asking me by email. I'd like to help, but I am very pressed for time. I prefer to pick the questions I answer from the Usenet news. Thus I can answer publicly at one go if I happen to have an answer. Besides, newsgroups have a number of readers who might know a better or an alternative answer. Don't be discouraged, though, if you get a reply like this from me. I am always glad to hear from fellow Turbo Pascal users. .................................................................... Prof. Timo Salmi Co-moderator of news:comp.archives.msdos.announce Moderating at ftp:// & http://garbo.uwasa.fi archives 193.166.120.5 Department of Accounting and Business Finance ; University of Vaasa ts@uwasa.fi http://uwasa.fi/~ts BBS 961-3170972; FIN-65101, Finland -------------------------------------------------------------------- 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? -------------------------------------------------------------------- From ts@uwasa.fi Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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 Thu Mar 7 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} --------------------------------------------------------------------