From ts@uwasa.fi Sat Mar 30 00:00:00 1996
Subject: FAQPAS4.TXT contents

                               Copyright (c) 1993-1996 by Timo Salmi
                                                 All rights reserved

FAQPAS4.TXT The fourth set of frequently (and not so frequently)
asked Turbo Pascal questions with Timo's answers. The items are in
no particular order.

You are free to quote brief passages from this file provided you
clearly indicate the source with a proper acknowledgment.

Comments and corrections are solicited. But if you wish to have
individual Turbo Pascal consultation, please post your questions to
a suitable Usenet newsgroup like news:comp.lang.pascal.borland. It
is much more efficient than asking me by email. I'd like to help,
but I am very pressed for time. I prefer to pick the questions I
answer from the Usenet news. Thus I can answer publicly at one go if
I happen to have an answer. Besides, newsgroups have a number of
readers who might know a better or an alternative answer. Don't be
discouraged, though, if you get a reply like this from me. I am
always glad to hear from fellow Turbo Pascal users.

....................................................................
Prof. Timo Salmi   Co-moderator of news:comp.archives.msdos.announce
Moderating at ftp:// & http://garbo.uwasa.fi archives  193.166.120.5
Department of Accounting and Business Finance  ; University of Vaasa
ts@uwasa.fi http://uwasa.fi/~ts BBS 961-3170972; FIN-65101,  Finland

--------------------------------------------------------------------
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 do I make an executable of my Turbo Pascal source program?
90) How can I quickly read the last byte of a file?
91) Is 2000 a leap year? What is the leap year algorithm?
92) Does anybody have a program that gives the week number?
93) How can I use OutText to write numbers in the graphics mode?
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:16 1996
Subject: Usenet Pascal newsgroups

76. *****
 Q: What are the current Pascal newsgroups on the Usenet news?

 A: The following new Pascal newsgroups were created June 12, 1995
to replace the old comp.lang.pascal. The following new Delphi
newsgroups were created around July 10, 1995.

A special note about Delphi postings. Please use the new delphi
newsgroups for the Delphi related postings. In particular, don't let
the names mislead you. The newsgroup comp.lang.pascal.borland does
NOT cover Delphi.

A second special note. Please avoid crossposting between the Pascal
newsgroups. In particular do not crosspost between the old
comp.lang.pascal and the new Pascal newsgroups. It is slows the
transition to the new system. (This automatic posting breaches the
non-crossposting tenet only because it is relevant information about
the arrangements of all the Pascal newsgroups.)

NEW:
 comp.lang.pascal.ansi-iso Pascal according to ANSI and ISO standards.
 comp.lang.pascal.borland  Borland's Pascal incl. Turbo Pascal (not Delphi!)
 comp.lang.pascal.mac      Macintosh based Pascals.
 comp.lang.pascal.misc     Pascal in general and ungrouped Pascals.

 comp.lang.pascal.delphi.databases     Database aspects of Borland Delphi.
 comp.lang.pascal.delphi.components    Writing components in Borland Delphi.
 comp.lang.pascal.delphi.misc          General issues with Borland Delphi.

RELATED of potential interest:
comp.os.msdos.programmer.turbovision Borland's text application libraries

OLD:
 comp.lang.pascal          Discussion about Pascal. (Please cease using!)

For more information about the new Pascal newsgroups please see

 52703 Jun 14 21:37 ftp://garbo.uwasa.fi/pc/doc-net/pasgroup.zip
 pasgroup.zip Information about the comp.lang.pascal.* newsgroups

 18086 Jul 11 08:18 ftp://garbo.uwasa.fi/pc/doc-net/delphi.zip
 delphi.zip Vote results of the comp.lang.pascal.delphi.* newsgroups

If your site is not getting the new Pascal newsgroups, please
contact your own site's newsmaster about the situation.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:17 1996
Subject: Capslock status and toggling

77. *****
 Q: How do I detect the CapsLock status, how do I turn it on/off?

 A: Here are the relevant Turbo Pascal routines in answer to these
questions.
  {}
  Uses Dos;  { The Dos unit is needed }
  {}
  (* Is CapsLock on *)
  function CAPSONFN : boolean;
  var regs      : registers;
      KeyStatus : byte;
  begin
    FillChar (regs, SizeOf(regs), 0);
    regs.ax := $0200;      { Get shift flags }
    Intr ($16, regs);      { The keyboard interrupt }
    KeyStatus := regs.al;  { AL = shift status bits }
    if (KeyStatus and $40) > 0 then         { bit 6 }
      capsonfn := true
    else
      capsonfn := false;
  end;  (* capsonfn *)
  {}
  (* Set CapsLock. Use true to turn on, false to turn off *)
  procedure CAPS (TurnOn : boolean);
  var keyboardStatus : byte absolute $0040:$0017;
      regs           : registers;
  begin
    if TurnOn then
       keyboardStatus := keyboardStatus or $40
     else
       keyboardStatus := keyboardStatus and $BF;
    { Interrrupt "check for keystroke" to ensure the LED status }
    FillChar (regs, SizeOf(regs), 0);
    regs.ah := $01;
    Intr ($16, regs);
  end;  (* caps *)
  {}
As you see, CapsLock is indicated by bit 6. The other toggles can be
handled in an equivalent way using this information about the memory
location Mem[$0040:$0017]:
  ScrollLock = bit 4      $10  $EF
  NumLock    = bit 5      $20  $DF
  CapsLock   = bit 6      $40  $BF
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:18 1996
Subject: Detecting F11 and F12

78. *****
 Q: How do I detect if the F11 or F12 key has been pressed?

 A: Here is a sample program
  uses Dos;
  (* Enhanced keyboard ReadKey, no Crt unit needed. Detects also F11
     and F12, and distinguishes between the numeric keypad and the
     gray keys. Lower part of the word returns the first scan code,
     the higher part the second *)
  function RDENKEFN : word;
  var regs     : registers;
      keyboard : byte absolute $40:$96;
  begin
    rdenkefn := 0;
    if ((keyboard shr 4) and 1) = 0 then exit;
    FillChar (regs, SizeOf(regs), 0);
    regs.ah := $10;
    Intr ($16, regs);
    rdenkefn := regs.ax;
  end;  (* rdenkefn *)
  {}
  procedure TEST;
  var key : word;
  begin
    while Lo(key) <> 27 do  { esc exits }
      begin
        key := RDENKEFN;
        if (Lo(key) = 0) and (Hi(key) = 133) then
          writeln ('F11 was pressed');
        if (Lo(key) = 0) and (Hi(key) = 134) then
          writeln ('F12 was pressed');
      end;
  end;
  {}
  begin TEST; end.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:19 1996
Subject: Substrings from a string

79. *****
 Q: How do I extract (parse) substrings from an input string?

 A: Carefully study these two routines which I have included in
 19593 Jun 1 12:12 ftp://garbo.uwasa.fi/pc/research/simirr10.zip
 simirr10.zip Deriving IRR from ARR: A Simulation Testbench, TS+IV
They use space (and anything in ascii below it) as the separator.
Change the while tests if you wish to have a different set of
separators.
  (* Number of substrings in a string *)
  function PARSENFN (sj : string) : integer;
  var i, n, p : integer;
  begin
    p := Length(sj);
    n := 0;
    i := 1;
    repeat
      while (sj[i] <= #32) and (i <= p) do Inc(i);
      if i > p then begin parsenfn := n; exit; end;
      while (sj[i] > #32) and (i <= p) do Inc(i);
      Inc(n);
      if i > p then begin parsenfn := n; exit; end;
    until false;
  end;  (* parsenfn *)
  {}
  (* Get substrings from a string *)
  function PARSERFN (sj : string; PartNumber : integer) : string;
  var i, j, n, p : integer;
      stash      : string;
  begin
    if (PartNumber < 1) or (PartNumber > PARSENFN(sj)) then
      begin PARSERFN := ''; exit; end;
    p := Length(sj);
    n := 0;
    i := 1;
    repeat
      while (sj[i] <= #32) and (i <= p) do Inc(i);
      Inc(n);
      if n = PartNumber then
        begin
          j := 0;
          while (sj[i] > #32) and (i <= p) do
            begin
              Inc(j);
              stash[0] := chr(j);
              stash[j] := sj[i];
              Inc(i);
            end;
          PARSERFN := stash;
          exit;
        end
       else
         while (sj[i] > #32) and (i <= p) do Inc(i);
    until false;
  end;  (* parserfn *)
  {}
  {... A separate, but useful function from the same package ...}
  (* Delete trailing white spaces etc rubble from a string *)
  function TRAILFN (sj : string) : string;
  var i : byte;
  begin
    i := Length (sj);
    while (i > 0) and (sj[i] <= #32) do i := i - 1;
    sj[0] := chr(i); trailfn := sj;
  end;  (* trailfn *)
  {}
  {... Another separate, but useful function from the same package ...}
  (* Delete leading white spaces etc subble from a string *)
  function LEADFN (sj : string) : string;
  var i, p : byte;
  begin
    p := Length (sj); i := 1;
    while (i <= p) and (sj[i] <= #32) do i := i + 1;
    leadfn := Copy (sj, i, p-i+1);
  end;  (* leadfn *)
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:20 1996
Subject: Size of a file

80. *****
 Q: How do I find out the size of any kind of a file?

 A1: Well, to begin with the FileSize keyword and an example code
are given in the manual (and help function of later TP versions) so
those, as usual, are the first places to look at. But the example
solution can be somewhat improved, and there is also an alternative
solution. The FSIZEFN should never be applied on an open file.
  function FSIZEFN (filename : string) : longint;
  var fle    : file of byte;  { declare as a file of byte }
      fmSave : byte;
  begin
    fmSave := FileMode;       { save the current filemode }
    FileMode := 0;            { to handle also read-only files }
    assign (fle, filename);
    {$I-} reset (fle); {$I+}  { to do your own error detection }
    if IOResult <> 0 then begin
      fsizefn := -1; FileMode := fmSave; exit;
    end;
    fsizefn := FileSize(fle);
    close (fle);
    FileMode := fmSave;       { restore the original filemode }
  end; (* fsizefn *)

 A2: The second, general alternative is
  uses Dos;
  function FSIZE2FN (FileName : string) : longint;
  var FileInfo : SearchRec;   { SearchRec is declared in the Dos unit }
  begin
    fsize2fn := -1;           { return -1 if anything goes wrong }
    FindFirst (filename, AnyFile, FileInfo);
    if DosError <> 0 then exit;
    if (FileInfo.Attr and VolumeId = 0) and
       (FileInfo.Attr and Directory = 0) then
         fsize2fn := FileInfo.Size;
  end;  (* fsize2fn *)

 A3: The third alternative is due to a Usenet posting by Wayne
Hoxsie (hoxsiew@crl.com). This alternative is an instructive example
of using file handles.
  uses dos;
  var f : file;
  {}
  function filelength (var f : file) : longint;
  var
    handle : ^word;
    regs : registers;
  begin
    handle := @f;
    fillchar (regs, SizeOf(regs), 0);   { just in case }
    regs.ax := $4202;
    regs.bx := handle^;
    regs.cx := 0;
    regs.dx := 0;
    msdos(regs);
    filelength := (longint(regs.dx) SHL 16)+regs.ax;
  end;
  {}
  begin
    assign(f,paramstr(1));
    filemode := 0;  { read-only files too }
    reset(f);
    writeln(filelength(f));
    close(f);
  end.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:21 1996
Subject: Formatting graphics output

81. *****
 Q: How do I format graphics output like in textmode writeln?

 A: In the graphics mode the positioned text output procedure is
OutTextXY (X ,Y : integer; TextString : string); It does not have
the same output formatting capabilities as the write procedure. It
only accepts the one TextString. Therefore all the output formatting
must be done previously on the string. The Str procedure has such
capabilities. The example below gives the rudiments.
  uses Crt, Graph;
  var grDriver : integer;
      grMode   : integer;
      ErrCode  : integer;
      s, s1    : string;
      v1       : real;
  begin
    grDriver := Detect;
    InitGraph (grDriver, grMode, ' ');
    ErrCode := GraphResult;
    if ErrCode <> grOk then begin
      Writeln ('Graphics error:', GraphErrorMsg(ErrCode)); halt; end;
    ClearDevice;
    {}
    { Writing text in the graphics mode }
    { Set the drawing color }
    SetColor (Yellow);
    { Set the current background color }
    SetBkColor (Black);
    { Set style for text output in graphics mode }
    SetTextStyle (DefaultFont, HorizDir, 2);
    { Preprocess the text }
    v1 := 2.345;
    Str (v1 : 10:2, s1);
    s := 'The first value is' + s1 + '.';
    { Output the text }
    OutTextXY (100, 30, s);
    OutTextXY (100, 50, 'Press any key');
    {}
    repeat until KeyPressed;
    {}
    RestoreCrtMode;
    writeln ('That''s all folks');
    CloseGraph;
  end.
Besides not having the same output formatting capabilities OutTextXY
and OutText procedures do not scroll the screen. If you wish to
achieve such an effect, you will have to code it yourself step by
step. You can see the effect in
 111673 Oct 8 1993 ftp://garbo.uwasa.fi/pc/ts/tsdemo16.zip
 tsdemo16.zip Assorted graphics demonstrations of functions etc
Coding the scrolling is a straight-forward but a laborious task.
Hence it is beyond this FAQ. The outline, however, is that you must
keep track where on the screen you are. When you come to the bottom
of your window you have to move the above region upwards before you
output new text. You can move graphics regions using the ImageSize,
GetImage and PutImage procedures.
  As for readln-type input in a graphics mode, that is a complicated
issue. You will have to build the input routine reading a character
at a time with ReadKey. The rudiments of using ReadKey are shown in
the first question of FAQPAS.TXT. The demo, referred to a few lines
back, will show the effect.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:22 1996
Subject: Reading more than one key

82. *****
 Q: How do I detect if more than one standard key is pressed down?

 A: The example code below relies very heavily on a Usenet posting
by Lou Duchez ljduchez@en.com who wishes to acknowledge Bill Seiler
for the handling of ports. The KeyNrDown and TEST routines are by
myself. Besides being a demonstration the TEST procedure can be used
to get the scan codes of the different keys instead of relying on
external documentation.
  Uses Dos;
  {}
  var keydown: array[0..127] of boolean;   { status array }
      oldkbdint: procedure;       { points to the "normal" keyboard handler }
      port60h, port61h: byte;     { used within the interrupt for storage }
  {}
  { The replacement keyboard handler }
  procedure newkbdint; interrupt;
  begin
    port60h := port[$60];
    keydown[port60h and $7f] := (port60h <= $7f);
    port61h := port[$61];
    port[$61] := port61h or $80;
    port[$61] := port61h;
    port[$20] := $20;
  end;
  {}
  { Get the scancode of the key pressed down, 128 for none }
  function KeyNrDown : byte;
  var i : byte;
  begin
    KeyNrDown := 128;
    for i := 0 to 127 do if KeyDown[i] then KeyNrDown := i;
  end;
  {}
  { Test by displaying the scan codes of the keys pressed }
  procedure TEST;
  var k, k1 : byte;
  begin
    k1 := 128;
    repeat
      k := KeyNrDown;
      if k <> k1 then begin
        write (k, ' ');
        if (k1 = 30) and (k = 31) then writeln ('Pressed A and S ');
        k1 := k;
      end;
    until k = $01; {escape}
  end; {test}
  {}
  begin
    { turn on the replacement keyboard handler }
    fillchar(keydown, 128, #0);  { sets array to all "false" }
    getintvec($09, @oldkbdint);  { record location of old keyboard int }
    setintvec($09, @newkbdint);  { this line installs the new interrupt }
    {}
    TEST;
    {}
    { turn off the replacement keyboard handler }
    setintvec($09, @oldkbdint);
  end.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:23 1996
Subject: Volume Serial Number

83. *****
 Q: How can I read a disk's Volume Serial Number?

 A: The Volume Serial Number for disks was introduced in MS-DOS
version 4.0. Here is an example code
  uses Dos;
  {}
  (* Convert a longint to a hexadecimal string *)
  function LHEXFN (decimal : longint) : string;
  const hexDigit : array [0..15] of char = '0123456789ABCDEF';
  var i         : byte;
      hexString : string;
  begin
    FillChar (hexString, SizeOf(hexString), ' ');
    hexString[0] := chr(8);
    for i := 0 to 7 do
      hexString[8-i] := HexDigit[(decimal shr (4*i)) and $0F];
    lhexfn := hexString;
  end;  (* lhexfn *)
  {}
  (* Get disk serial number. Requires MS-DOS 4.0+.
     Else, or on an error, returns an empty string.
     The default drive can be pointed to by using '0' *)
  function GETSERFN (drive : char) : string;
  type diskInfoRecordType =
    record
      infoLevel      : word;                   { zero }
      serialNumber   : longint;                { DWORD actually }
      volumeLabel    : array [1..11] of char;  { NO NAME if none present }
      filesystemType : array [1..8] of char;   { FAT12 or FAT16 }
    end;
  var regs     : registers;
      diskInfo : diskInfoRecordType;
      serial   : string;
  begin
    getserfn := '';
    if swap(DosVersion) < $0400 then exit;
    FillChar (regs, SizeOf(regs), 0);
    drive := UpCase (drive);
    if drive <> '0' then if (drive < 'A') or (drive > 'Z') then exit;
    regs.ah := $69;             { Interrrupt 21 function $69 }
    regs.al := $00;             { subfunction: get serial number }
    if drive <> '0' then
      regs.bl := ord(drive) - ord('A') + 1
      else regs.bl := 0;
    regs.ds := Seg(diskInfo);   { the diskInfo address: }
    regs.dx := Ofs(diskInfo);   { its segment and offset }
    Intr ($21, regs);
    if (regs.flags and FCarry) <> 0 then exit;  { CF is set on error }
    serial := LHEXFN (diskInfo.serialNumber);
    getserfn := Copy (serial, 1, 4) + '-' + Copy (serial, 5, 4);
  end;  (* getserfn *)
  {}
  begin
    writeln ('C: ', GETSERFN('C'));
  end.

 A2: The second alternative has been modified from a posting by
Robert B. Clark rclark@su1.in.net. I have also utilized INTERRUP.E
from Ralf Brown's listing of interrupt calls
 ftp://garbo.uwasa.fi/pc/programming/inter48b.zip
  {}
  uses Dos;
  function GETSERFN2 (drive : char): longint;
  var ParBlock : array [0..24] of char;  { IOCTL parameter block Table 0785 }
      regs     : registers;
      sernum   : longint;
  begin
    FillChar (ParBlock, SizeOf(ParBlock), 0);
    FillChar (regs, SizeOf(regs), 0);
    regs.ax := $440D;     { IOCTL - generic block device request }
    if drive <> '0' then  { '0' points to the default drive }
      regs.bl := ord(UpCase(drive)) - ord('A') + 1  { drive as byte }
      else regs.bl := 0;
    regs.ch := $08;       { block device IOCTL category code: disk drive }
    regs.cl := $66;       { IOCTL minor code: get volume serial number }
    regs.ds := Seg(ParBlock);   { Parameter block segment address }
    regs.dx := Ofs(ParBlock);   { Parameter block offset }
    MsDos (regs);         { Call interrupt $21 }
    if regs.Flags and FCarry = 0 then
      sernum := word(ord(ParBlock[4]) + ord(ParBlock[5]) shl 8) * 65536 +
                word (ord(ParBlock[2]) + ord(ParBlock[3]) shl 8)
    else sernum := 0;
    getserfn2 := sernum;
  end;  (* getsetfn2 *)
  {}
  begin
    writeln ('C: ', LHEXFN(GETSERFN2('0')));
  end.

 A3: Setting a disk's serial number, instead of just reading it, is
more complicated and will not be covered here. If you need it, the
routine without source code is available (for floppies only for
security reasons) as
  "SETSER Set floppy's serial number (MsDos 4.0+)"
in TSUNTK.TPU in ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip

--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:24 1996
Subject: Disabling the keyboard

84. *****
 Q: How can I disable and then enable the keyboard in my TP program?

 A: Here is the code. A warning! Don't experiment with ports. You
can do real harm to your data and your computer if you do not know
exactly what you are doing.
  uses Dos, Crt;  { Crt only needed because of 'Delay' in the testing }
  var i : byte;   { only needed in the testing }
      NormalKeyboard : procedure;
  {}
  procedure DisableKeyboard; interrupt;
  var port60, port61 : byte;
  begin
    port60 := Port[$60];  { KeyBoard controller data output buffer }
    port61 := Port[$61];  { Keyboard controller port B }
    Port[$61] := Port61 or $80;  { clear keyboard }
    Port[$61] := Port61;
    Port[$20] := $20;     { Programmable Intr. Contr. initialization }
  end;
  {}
  begin
    writeln ('Testing...');
    GetIntVec ($09, @NormalKeyboard);
    SetIntVec ($09, @DisableKeyboard);
    write ('The keyboard is now disabled..');
    for i := 1 to 5 do begin
      Delay (1000);
      write (i:2);
    end; {for}
    writeln;
    SetIntVec ($09, @NormalKeyboard);
    write ('The keyboard is now enabled...');
    for i := 1 to 5 do begin
      Delay (1000);
      write (i:2);
    end; {for}
  end.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:25 1996
Subject: CD-ROM device name

85. *****
 Q: How do I get the character device name of the (first) CD-ROM?

 A: First the code for a quick and dirty method to find the
character device name
  function MSCDEXFN : string;
  var s : string;
      f : text;
      i : byte;
      fmSave : byte;
  begin
    mscdexfn := '';                  { To indicate not found }
    fmSave := FileMode;              { Store the original file mode }
    FileMode := 0;                   { Also if read-only }
    Assign (f, 'c:\autoexec.bat');   { Browse the AUTOEXEC.BAT }
    {$I-} Reset (f); {$I+}
    if IOResult <> 0 then exit;      { AUTOEXEC.BAT not found }
    while not eof(f) do begin        { Line by line }
      readln (f, s);
      for i := 1 to Length(s) do s[i] := Upcase(s[i]);
      if Pos('MSCDEX', s) > 0 then begin      { Is this the line }
        if Pos ('REM', s) = 1 then continue;  { Skip rem lines }
        Close (f);
        FileMode := fmSave;          { Restore the original mode }
        i := Pos('/D:', s);          { Look for the switch }
        if i = 0 then exit;          { Nah! }
        i := i + 3;                  { Where the name should start }
        if i > Length(s) then exit;  { Nothing there! }
        s := Copy (s, i, 255);       { Rest of the line after /D: }
        mscdexfn := s;
        i := Pos (' ', s);
        if i = 0 then exit;
        mscdexfn := Copy (s, 1, i-1);
        exit;                        { Don't close twice }
      end; {if}
    end; {while}
    Close (f);
    FileMode := fmSave;              { Restore the original mode }
  end; (* mscdexfn *)

 A2: There is more general and orthodox solution to finding the
character device name for the (first)m CD-ROM. This was kindly
provided to me by Chris Rankin (rankin@shfax1.shef.ac.uk).
  uses Dos;
  function GetCDROMDevice : string;
  const driver_name_len = 8;
  type
    sig     = array[1..6] of char;
    siglet  = array[1..4] of char;
    signum  = array[1..2] of char;
    drvname = array[1..driver_name_len] of char;
    driverstr = string[driver_name_len];
  type
    PCDROMDriver = ^TCDROMDriver;
    TCDROMDriver = record
                     NextDriver:         PCDROMDriver;
                     DeviceAttr:         word;
                     StrategyEntryPoint: word;
                     INTEntryPoint:      word;
                     DeviceName:         drvname;
                     Reserved:           word;
                     DriveLetter:        byte;
                     Units:              byte;
                   case byte of
                     0: (SigLetters:     siglet;
                         SigNumbers:     signum);
                     1: (Signature:      sig)
                   end;
    TDriveEntry = record
                    SubUnit: byte;
                    Driver:  PCDROMDriver
                  end;
  var
    DeviceList: array[1..26] of TDriveEntry;
    Regs:       registers;
    Name:       driverstr;
  begin
    with Regs do
      begin
        ax := $1500;
        bx := 0;
        intr($2f,Regs);      (* Ask for number of CD-ROM drives. *)
        if bx = 0 then       (* If none, then exit.              *)
          begin
            Name[0] := #0;
            GetCDROMDevice := Name;
            exit
          end;
        ax := $1501;           (* Put information about each CD-ROM *)
        es := seg(DeviceList); (*  into DeviceList[].               *)
        bx := ofs(DeviceList);
        intr($2f,Regs)
      end;  (* Below: Name of first CD-ROM driver *)
    Name := DeviceList[1].Driver^.DeviceName;
    while Name[length(Name)] = ' ' do  (* Strip off trailing blanks.. *)
      dec(Name[0]);
    GetCDROMDevice := Name
  end;
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:26 1996
Subject: Ejecting CD-ROM

86. *****
 Q: How do I eject a CD-ROM using a Turbo Pascal program?

 A: The code for the ejection is given below. Note that it needs the
MSCDEXFN function from the previous FAQ item.
  uses Dos;
  {}
  procedure EJECT (charDev     : string;
                   var ok      : boolean;
                   var errCode : word);
  var regs        : registers;
      cdrom       : file;
      cdCtrlBlock : byte;            { CD-ROM Control Block }
      handle      : ^word;           { Handle referencing CD-ROM driver }
  begin
    Assign (cdrom, charDev);         { Character device for CD-ROM driver }
    {$I-} Reset (cdrom); {$I+}       { Tackle errors yourself }
    if IOresult <> 0 then begin      { Exit if file not found }
      ok := false;
      errCode := $FFFF;              { Your own arbitrary error code }
      exit;
    end;
    FillChar (regs, SizeOf(regs), 0);  { Just to make sure }
    regs.ax := $4403;                { Function $44, subfunction $03 }
    handle  := @cdrom;               { Establish the file handle }
    regs.bx := handle^;
    FillChar(CdCtrlBlock, SizeOf(CdCtrlBlock), 0);
    CdCtrlBlock := $00;              { $00 eject disk; $05 close tray }
    regs.ds := Seg(CdCtrlBlock);     { ds:dx CD-ROM control block }
    regs.dx := Ofs(CdCtrlBlock);
    MsDos (regs);                    { Call interrupt $21 }
    {$I-} Close (cdrom); {$I+}
    ok := regs.flags and FCarry = 0; { Success or not? }
    errCode := regs.ax;              { $01 = invalid function }
  end;                               { $05 = access denied }
  {}                                 { $06 = invalid handle }
  procedure TEST;                    { $0D = invalid data }
  var ok : boolean;
      code : word;
  begin
    EJECT ('K', ok, code);
    if ok then writeln ('Success') else writeln ('Error ', code);
  end;
  {}
  begin
    TEST;
  end.

My thanks are due to Miro Wikgren (wikgren@cc.helsinki.fi) who
pointed out that the "handle referencing character device for CD-ROM
driver" must be the name given when the CD-ROM driver is loaded in
CONFIG.SYS and AUTOEXEC.BAT. I could not solve this problem without
that help in comp.lang.pascal.borland. In fact the previous FAQ item
was tackled only after the current FAQ item had been solved first.

A slightly different approach to the file handle by Miro
  var cdrom : text; { CD-ROM is a character device }
  handle    : word; { Handle: word, not a pointer }
  :
  handle  := TextRec(cdrom).handle;  { Use TP help for more on this }
  regs.bx := handle;
  :

Another solution can be found in
 3427 Mar 15 18:35 ftp://garbo.uwasa.fi/pc/turbopas/cdtips01.zip
 cdtips01.zip Eject/Close/Lock/Unlock CD-ROM in TP for Win95, C.Rankin
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:27 1996
Subject: Detecting ANSI.SYS

87. *****
 Q: How do I find out if the ANSI.SYS driver has been loaded?

 A: The source code of the relevant function is given below.
However, this is not necessarily a good solution. First, it requires
at least MS-DOS version 4.0. Second, there are other, compatible
screen drivers like ZANSI.SYS. You probably are more interested if
such a screen driver has been installed rather than if it is
ANSI.SYS in particular. To find out if any compatible screen driver
is operative use ISANSIFN from TSUNTG.TPU from
 112570 Aug 16 1994 ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip
 tspa3470.zip Turbo Pascal 7.0 real mode units for (real:-) programmers
  uses Dos;
  function ANSIOKFN : boolean;
  var regs : registers;
  begin
    if swap(DosVersion) < $0400 then begin
      writeln ('Error: MS-DOS 4+ required');
      ansiokfn := false;
      halt;
    end;
    FillChar (regs, SizeOf(regs), 0);
    regs.ax := $1A00;
    Intr ($2F, regs);
    ansiokfn := regs.al = $FF;
  end; (* ansiokfn *)
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:28 1996
Subject: TP tutorial and books

88. *****
 Q: Where do I find Turbo Pascal tutorials and/or good textbooks?

 A: I'll list some useful sources. The first one (where also this
item comes from) among other things contains a slightly outdated
list of TP textbooks.

 ftp://garbo.uwasa.fi/pc/ts/tsfaqp29.zip
 tsfaqp29.zip Common Turbo Pascal Questions and Timo's answers

 ftp://garbo.uwasa.fi/pc/turbopas/tptutrXX.zip
 tptutrXX.zip Glenn Grotzinger's ascii-text Turbo Pascal Tutor

 ftp://garbo.uwasa.fi/pc/turbopas/tpr-book.zip
 tpr-book.zip Electronic Turbo Pascal Reference freeware book

 ftp://garbo.uwasa.fi/pc/doc-net/faqclpb.zip
 faqclpb.zip comp.lang.pascal.borland newsgroup Mini-FAQ

Furthermore, you should see 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.
   Yet another useful source can be the Turbo Pascal WWW pages. You
can find some of them by connecting to my WWW home page. Its address
is http://uwasa.fi/~ts. Select my collection of HTTP links and
proceed to the programming section on the link list.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:29 1996
Subject: Making an executable

89. *****
 Q: How do I make an executable of my Turbo Pascal source program?

 A: This is a typical beginner's frequent question which belies not
having read the manual carefully. You DO have the manual, right? If
you are using Turbo Pascal 7.0 this is explained on page 48 of the
User's Guide in the paragraph "Choosing a destination". Here, in
brief, is what you should do
  Press F10 to go to the main menu (or press alt-C)
  Choose Compile
  Choose Destination Disk  (toggle with enter)
To direct where the executable should go
  Press F10 to go to the main menu (or press alt-O)
  Choose Options
  Choose Directories...
  Edit the item EXE & TPU directory   (the destination directory)
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:30 1996
Subject: Last byte of a file

90. *****
 Q: How can I quickly read the last byte of a file?

 A: Below is the code for a relevant procedure. It has a number of
instructive details for you to look into. It is easy to expand this
procedure into showing any byte counted from the end by substituting
the 1 in Seek (f, fs-1) to the inverted position, and by taking care
that the position is not outside the file.
  procedure LASTBYTE (fname  : string; var lb : byte);
  var f      : file;       { Use an untyped file designation }
      fmSave : byte;       { To push and pop the FileMode }
      fs     : longint;    { For file size }
  begin
    fmSave := FileMode;    { Push the original FileMode }
    FileMode := 0;         { To enable reading also read-only files }
    Assign (f, fname);
    {$I-} Reset (f, 1); {$I+}     { Open file and set record size to 1 }
    if IOResult <> 0 then begin
      writeln ('Error opening file ', fname);
      halt;
    end;
    fs := FileSize(f);     { Get the size of the file }
    if fs = 0 then begin
      writeln ('Empty file ', fname);
      halt;
    end;
    Seek (f, fs-1);        { Position to the last byte of the file }
    BlockRead (f, lb, 1);  { Read the value of the position into lb }
    Close (f);             { Close the file }
    FileMode := fmSave;    { Pop the original FileMode }
  end; (* lastbyte *)
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:31 1996
Subject: Leap year

91. *****
 Q: Is 2000 a leap year? What is the leap year algorithm?

 A: With the approaching turn of the century this question is
becoming more and more common. Here is the algorithm in Turbo
Pascal.
 function ISLEAP (y : integer) : boolean;
 begin
   isleap := (y mod 4 = 0) and not ((y mod 100 = 0) and not (y mod 400 = 0));
 end;  (* isleap *)
My thanks are due to Dr. John Stockton and Associate Professor Seppo
Pynnonen for confirming the result. In fact it was who John
suggested adding this question to the FAQ.
   There are several equivalent formulations achieving the same
result. Also nested multi-line if statments could be used. The
boolean statements are much more concise, even if not very easy to
construct.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:32 1996
Subject: Week number

92. *****
 Q: Does anybody have a program that gives the week number?

 A: This answer comes without source code just with a pointer to a
TPU including a week number algorithm. There is a function
 "WEEKNRFN Returns the week number for a given date"
in the TSUNTE.TPU unit in my
 112570 Aug 16 1994 ftp://garbo.uwasa.fi/pc/ts/tspa3470.zip
 tspa3470.zip Turbo Pascal 7.0 real mode units for (real:-) programmers.
(The unit collection is also available for earlier TP versions.)
--------------------------------------------------------------------

From ts@uwasa.fi Sat Mar 30 00:01:33 1996
Subject: OutText, integers and reals

93. *****
 Q: How can I use OutText to write numbers in the graphics mode?

 A: OutText is the procedure to use for output in the graphics mode.
The syntax of the procedure is OutText(TextString: string). You'll
first have to convert a number into a string before you can output
it with OutText. The example below shows how it can be done when the
users wishes to output the integer value value of 12 and the result
of 4/7 as a real with a suitable formatting. Generalization from
thereon should be easy.
  uses Crt, Graph;
  var grDriver : integer;
      grMode   : integer;
      ErrCode  : integer;
  const CharSize : integer = 2;
  {}
  function INT2STR (x : integer; ff : byte) : string;
  var s : string;
  begin
    Str (x : ff, s);
    int2str := s;
  end;
  {}
  function REAL2STR (x : real; ff, dd : byte) : string;
  var s : string;
  begin
    Str (x : ff : dd, s);
    real2str := s;
  end;
  {}
  begin
    grDriver := Detect;
    InitGraph (grDriver, grMode, ' ');
    ErrCode := GraphResult;
    if ErrCode <> grOk then begin
      Writeln ('Graphics error:', GraphErrorMsg(ErrCode)); halt; end;
    SetColor (LightCyan);
    SetBkColor (Black);
    SetTextStyle(DefaultFont, HorizDir, CharSize);
    {}
    {... this is the example's key line ...}
    OutText ('The values are: ' + INT2STR(12,2) + REAL2STR(4/7,10,3));
    {}
    MoveTo (0, 10*CharSize);
    OutText ('Press any key');
    repeat until KeyPressed;
    RestoreCrtMode;
    CloseGraph;
  end.
Naturally, the 12 in INT2STR(12,2) could as well be a variable
containing the value. Ditto for REAL2STR(4/7,10,3).
--------------------------------------------------------------------

