





     Issue #10                                                   March, 1992
     ========================================================================
           The Pascal Newsletter (C) Copyright 1992 by Alex Boisvert
                                ALL RIGHTS RESERVED

                           [][][][][]   []        []    []
                          []      []   [][]      []    []
                         []      []   [] []     []    []
                        [][][][][]   []   []   []    []
                       []           []    []  []    []
                      []           []     [] []    []
                     []           []      [][]    []
                    []           []        []    [][][][][]

      T H E   I N T E R N A T I O N A L   P A S C A L   N E W S L E T T E R

     ========================================================================

     Table of Contents

     (*) Introduction .................................................  2
              by Alex Boisvert - Editor, Writer
     (*) BigFiles - A Large File Viewer................................  3
              by Mich Davis - Contributing Writer
     (*) BigArrays - You've Never Seen Arrays This BIG!................ 10
              by Mich Davis - Contributing Writer
     (*) High-Quality Sound with Turbo Pascal.......................... 13
              by Alex Boisvert - Editor, Writer
     (*) Overlays - How to fit your programs in memory................. 15
              by Alex Boisvert - Editor, Writer
     (*) Turbo Vision Without Getting GUI.............................. 19
              by Richard Morris - Editor Over-the-Pond
     (*) Answers to Frequently Asked Questions ........................ 26
              by Trevor Carlsen - Contributing Writer
     (*) Running 80286 programmes on 8088/8086 Computers............... 37
              by David J. N. Begley - Contributing Writer
     (*) Conclusion ................................................... 41
              by Alex Boisvert - Editor, Writer


     The Pascal Newsletter is published by Alex Boisvert as a medium for  all
     Pascal programmers to share ideas,  comments,  techniques or experiences
     concerning any subject related to the Pascal programming language.

     DataMAX  BBS  is  the  home  of  PNL.   It can be reached in Sherbrooke,
     Quebec,  Canada at (819) 563-6327 or FidoNet @ 1:167/405.0 between 23:00
     and 8:00 Eastern Standard Time.

     Articles and source code submitted by others are  the  property  of  the
     authors  and  are  used  with  permission.    They  may  not  be treated
     separately from this newsletter without the author's permission and thus
     maintain all distribution rules of the newsletter as a whole.  It may be
     freely distributed in un-modified form,  but no charge whatsoever may be
     incurred on  the  recipient.    All  code  is  provided  'as-is' with no
     guarantees whatsoever.

     ========================================================================
      PNL #10                      Page 2                         March, 1992



     =================================================================
                     Introduction - Editor's Note
     =================================================================

     After more than a year of silence, the Myth becomes a reality.
     The Pascal NewsLetter has revived.

     Many people will ask themselves why this is issue #10 and why wasn't PNL
     published during the last year.

     The facts.   Pete Davis,   founder  and  ex-editor of PNL,  had personal
     problems and was forced to sell his computer.  He was unable to continue
     his work.

     I must thank him for the outstanding work he has  done  with  PNL.    He
     worked very hard to keep the issues out and each of them was a success.

     For the sake of the Pascal community, I am taking up the baton.   I will
     be  publishing the Pascal NewsLetter starting with issue #10 and up.   I
     expect to  be  publishing  one  issue  bimontly,   if  I have sufficient
     material (articles) to fill it.   Since  I  am  starting  from  scratch,
     everything  has  to be done a second time.   I must find new columnists,
     contributing writers and people to  distribute the newsletter all around
     the world.   Richard Morris will be distributing it in Australia but,  I
     have yet to find a distributor in Europe and, possibly, in Asia.

     As you may have noticed,  I  have  changed  a  bit  the  format  of  the
     newsletter.    The table of contents now shows on the first page.   This
     will be handy if you are keeping the newletters on print.

     I hope you all enjoy  the  "New  PNL"  and  I  wish that more people get
     interested in it.

     Alex Boisvert
     PNL's New Chief Editor.
      PNL #10                      Page 3                         March, 1992



     =================================================================
                       BigFiles - a large file viewer
     =================================================================

     G'Day!  Well, it sure is good to see the Pascal NewsLetter alive and well
     again.   In this article and the followup in the next edition,  we'll  be
     working  on  a  very  useful  tool - a technique for accessing large text
     files.

     On the way we'll be exploring a  LOT of really neat techniques all rolled
     into the one program.   Unless you're a closet guru, I'm SURE you'll find
     something you haven't come across yet.  We'll be dabbling in:

     o Take-only-what-you-need dynamic memory allocation.
     o Vertical Scrolling.
     o Turbo Pascal's Built-in Assembler.
     o Using DOS to allocate memory.
     o Treating TEXT files as TYPED files.
     o Buffering Text files to improve access speed.
     o A technique for quick access to any part of a text file.
     o Objects.
     o Very large arrays (bigger than 64k in size, or more than 64k
       elements).
     o A standalone program for viewing text files, a la Buerg's LIST.
     o i286-capable programs.
     o Horizontal scrolling.
     o Multitasking.
     o Turbo Vision.
     o Optimization using Turbo Profiler.
     o Optimization using the built-in assembler.
     o DesqView-aware programs.

     This is all done with Turbo Pascal 6.0.   I apologise to those who  don't
     have 6.0, but that's progress.

     Part 1:

     [Note: I have a 286, so I compile my programs with $G+ on.  If you don't,
     remove  the  $G+  from  compiler  options,   and  the  "Test186" from the
     uses-block of each program it appears in.]

     What we aim to have by the end of this article is a tool which will allow
     us to view arbitrarily large text  files.   Each of the included programs
     builds on the one before it, starting with MDP1.PAS.


     The way we'll first explore is to read the lines  of  the  file  into  an
     array  of strings.   MDP1.PAS is about as simple as you can get!   It has
     two sections - the first section opens the file, and the second prints it
     forwards and backwards.   Note that this  series of programs get the name
     of the file to show from the command line,  ie MDP1 c:\autoexec.bat.   If
     you forget to give it a filename,  the program will try to read from  the
     keyboard.    You can exit by pressing ^Z and Enter.   Note that IDE users
     can set the  command-line  parameters  by  selecting [R]un,  p[a]rameters
     inside the IDE, and [R]un, [A]rguments inside Turbo Debugger.

     Note that this program is extremely limited:
      PNL #10                      Page 4                         March, 1992



     o It is wasteful of resources to declare an array larger than we'll
       need, but for many files, 100 lines won't be enough.

     o The maximum number of lines we can possibly extend this approach to,
       is about 250 lines.

     For these reasons, MDP1.PAS is a bit of an evolutionary dead-end.

     MDP2.PAS is a little better.   This program works  by  reading  the  file
     line-by- line, and storing it IN ONLY AS MUCH MEMORY AS IT NEEDS to store
     it.    Thus,   files with (on average) shorter lines will be able to have
     more lines loaded before the computer runs out of memory.

     This program introduces the idea of a  table to point to the start of the
     lines in the file.  Once we have this table, it's a simple matter to look
     up the line, get the address of that line in memory and print it out.

     Note the table is made of records,  with a 4-byte pointer field  for  the
     start of the line, and a word for the size of the line.   We need to keep
     the  size so that later we can feed the size to FreeMem - TP doesn't keep
     track of how big the memory chunk it allocates you with GetMem is, so you
     have to remind it at FreeMem time.

     MDP2.PAS is limited by two things -  It stores the lines from the file on
     the heap,  so can only handle files of about half  a  megabyte  or  less.
     Secondly,   the table of line pointers is limited to about 10000 entries,
     since the address table can't  be  larger  than 64k (ie,  a little larger
     than 6*10000).

     Now let's turn our attention to something different - scrolling.   Take a
     look at MDP3.PAS.

     Perhaps the best way to see what it does is to run  it.    When  you  do,
     you'll see a line of numbers, which you can move along by using the + and
     - keys.

     This  program has two parts.   The first draws the initial screen.   Note
     the call to min (minimum) in  the  For - this roughly translates to "draw
     until you hit the end of the screen, or until you run out of numbers".

     The second part of the program is the standard  repeat-until/readkey/case
     input  loop.    WinTop  by-the-way  is the line number which is currently
     displayed at the top of the screen.

     Note that the - and +  sections  scroll the screen by using Crt's DelLine
     and InsLine.   An unfortunate consequence of this is that the status line
     at the bottom of the screen flickers quite  badly  when  scrolling  takes
     place.  To stop this, you'd either have to put the status line at the top
     of the screen, or declare a Crt.Window.  I've done neither since I didn't
     find  it  all  that annoying.   The followup article in the next PNL WILL
     address this problem.

     It's a good idea to step through this  program using F7 to get an idea of
     just how it performs the scrolling.   Note that after the initial redraw,
     only the lines that are just appearing are rewritten -  this  is  faster,
     and  reduces  the inevitable screen flash (not CGA snow) that occurs when
     you update a screen line-by-line.
      PNL #10                      Page 5                         March, 1992



     Now let's have a look at MDP4.PAS.    This  is a hybrid - it combines the
     file reading section from MDP1.PAS with the scroller from MDP3.PAS, so it
     shouldn't contain too  many  surprises.    Note  that  in  the  scrolling
     section,  the writeln's which outputted numbers have been replaced with a
     reference to the data at that line position (eg, line [WinTop]).  This is
     then passed to procedure PrLn,  which acts like a writeln, except that it
     chops  off  lines  that  are longer than 79 characters.   If you don't do
     this, a long line will overflow onto the next, spoiling the display.

     Until now,  the programs  we've  looked  at  have been fairly elementary.
     Well,  you've been warned!   Programs after this point get  quite  a  bit
     harder to understand, for many reasons:

     o They're more sophisticated.
     o They use techniques not seen in everyday Turbo Pascal programs.
     o I wrote them,  and you're having my less-than-perfect programming style
       inflicted on you.  Sorry!  :-D

     Let's look at three interesting areas before we proceed to MDP5.PAS.  The
     first  is  a  device  which  dramatically  speeds  up  TEXT file access -
     SetTextBuf.

     SetTextBuf is part of Turbo Pascal's Run-Time Library [RTL].  It lets you
     tell Turbo Pascal to use a given area of memory as a cache or buffer when
     dealing with a text file.    Let's  say  we had the following fragment of
     code:

     var TBuff:array [0..8191] of byte;
          f:text;
          line:string;

     begin
       assign (f,'whatever.doc');
       SetTextBuf (f,TBuff);
       reset (f);
       readln (f,line); readln (f,line);
       close (f);
     end;

     Without the SetTextBuf,  the two readlns would probably  have  cause  two
     disk accesses.   Instead, the RTL will now try to fill TBuff with as much
     data  from  f as it can.   Then,  requests to access the file come out of
     TBuff, instead of an access to the disk.

     If you don't already,  try it in  your next program which deals with lots
     of text files - you'll be pleasantly surprised at the speed increase!

     The second concept overcomes an annoying limitation with Turbo  Pascal  -
     when  you're  reading  TEXT  files,  you have no way of using the FilePos
     function to find out where you  are,  or the Seek procedure to reposition
     yourself.  To get around this, let me introduce to you ("How do you do?")
     a unit written by a fellow Australian - TextUtl2.   This  unit  lets  you
     perform  FilePos,   FileSize  and Seek on text files,  and is a real life
     saver!

     Third,  the idea  of  going  to  DOS  for  memory,   rather than Pascal's
     GetMem/FreeMem.   Why?   Because Turbo Pascal is limited to  handing  you
     blocks  of  64k at a time,  whereas DOS will happily give you a couple of
      PNL #10                      Page 6                         March, 1992



     hundred k's of consecutive  memory,   which  we'll  be using for our mega
     arrays.

     Take a look at MDP5.PAS.   The first thing to note is that now  we  don't
     read  in  the  actual  data  from  the file - all we do is store the file
     OFFSET of each line.   Later when we want to go to a certain line,  we'll
     look up its offset,  Seek to that position, and happily read away.   This
     lets us view up to 16000 lines,   which is the limit of Vern Buerg's LIST
     program.

     Observe how the text buffer is set up:  TBuffSize is set to how many K we
     want the buffer to be (I've picked 20k).   Then TBuffType is declared  to
     be  an array of that size in K's.   Note that the buffer differs from the
     code fragment we saw before,  because it'll come off the heap, instead of
     being a static variable.

     Note the call to TextFilePos on line 47.   This shows just how easy it is
     to use the TEXTUTL2 unit.   Before each line is read, we store the offset
     for that line for future reference.

     If you're wide awake,  you'll see  that  line  48 we use a readln without
     telling it where to put the data it reads.   This is  because  we're  not
     interested in what's in the line, just where they start.   The reading of
     the data will be done later.

     So,   the  program reads in the file,  and asks you for the starting line
     number.  When you've typed a valid number in, line 64 looks up the offset
     of that line,  and moves the  file  pointer to that position.   The lines
     following that read the file to cover the screen, either until the bottom
     of the screen is hit, or the end of the file occurs.

     Note line 71 - this is a shady trick!   If you use Crt.KeyPressed,   then
     even when it detects a key it leaves it in the keyboard buffer.   So, the
     program pauses at line 71 until you press a key.   When you do,  it loops
     back  to  the readln in line 61,  which gets its first key free - the one
     you pressed.

     This trick lets you prompt for information  in a way which isn't going to
     spoil your nice screen layout until the user is ready to type something.

     Note that you only have to perform ONE TextSeek for a whole screen.   You
     COULD perform a TextSeek for EVERY line on the screen,  but  why  bother?
     Reading  a  line  leaves  the file pointer (and cursor!) sitting fair and
     square on the next line, all ready for you to use.

     Note in line 75 we Dispose of TBuff AFTER the file has been closed.

     Now we're going to sidetrack for a moment.  Just for an experiment, we're
     going to ask DOS for the memory for TBuff,  instead of TP, which uses the
     heap.

     This program will be called  MDP5A.PAS  ("A" because it's an experiment).
     But wait!   Do I hear you say there's  no  MDP5A.PAS  included?    You're
     right.   Instead, I've put the DIFFERENCES between MDP5.PAS and MDP5A.PAS
     into  a  file  called MDP5A.EDL.   This file is really a script for EDLIN
     (and you thought you'd never  use  EDLIN again eh?!).   To get MDP5A.PAS,
     type this at the command line:
      PNL #10                      Page 7                         March, 1992



     C:\PNL010> edlin MDP5A.PAS < MDP5A.EDL

     ...  and this will create MDP5A.PAS.

     In fact,  I've built a Turbo Vision revision control  system  which  uses
     purely  DOS  commands to do the nitty gritty work - it compares two files
     with FC, and generates an Edlin script for the differences.   I'm telling
     you this because it'll be in  a  future edition of the Pascal Newsletter,
     so keep reading!   (And keep writing,   else  there  won't  BE  a  future
     edition!)

     Note  that MDP5A.PAS uses a unit called DosMem.   I've written this as an
     easy-to-use interface to  the  DOS  memory  functions.    There are three
     modules:

     function Alloc(paras:word): word;
     procedure Free(p:word);
     function Largest: word;

     Note that each of these works with  "paragraphs",   which  on  PCs  means
     "16-bytes".    Alloc  is  like  TP's  GetMem  -  it'll  give  you "paras"
     paragraphs of memory,  returning with the  segment of the block.   If the
     request can't be met,  it will return a segment of zero.   Free  is  like
     TP's  FreeMem  -  it'll  hand  the  block  at "p" back to DOS when you've
     finished with it.   Note that you  don't  have  to tell DOS how large the
     block was - now if only FreeMem would do that!    Largest  is  like  TP's
     MaxAvail  -  it'll tell you the largest size block you can ask for.   The
     result is in paragraphs.

     Note that  the  DosMem  unit  is  written  using  the Built-in assembler.
     Sorry,  pre-6.0 people.   Needle me enough and I'll write you a  .OBJ  or
     some inline code which does it.

     Ok, back to business.  Line 39 of MDP5A.PAS performs the same function as
     line  39  in  MDP5.PAS,  except that it asks DosMem.Alloc for the memory.
     Because DosMem uses paragraphs, we have to convert the buffer size in K's
     (20) to paragraphs.   We do this by multiplying by 64.   (20k * 64 = 1280
     paras = 1280 * 16 = 20480 bytes = 20k).

     Note that TBuff is a pointer type, with two halves - the segment, and the
     offset.   The segment comes from the result of Alloc, and the offset will
     always be 0. The two are  joined  into  a  standard TP pointer by the Ptr
     function.

     The other lines that have changed (57 and 75) pass the segment  of  TBuff
     back to DOS - note that you do this AFTER the file is closed.

     The  reason  we  change a perfectly good program (MDP5.PAS) into one that
     has to rely on DOS (!) is to give you a feel for working with DOS memory,
     which is what we'll be doing in the next program.

     MDP6.PAS is the first of our programs to use the BigArray unit,  which is
     covered in another PNL  article.    Basically,   the BigArray unit has an
     object type called BigDOSArray,  which makes use of the >64k  ability  of
     DOS's memory management to implement a VERY large array.

     We'll  be using one mega-array,  and our data type is the longint,  which
     takes 4 bytes.  So, line 44 tells the array object all about it.  Line 45
      PNL #10                      Page 8                         March, 1992



     asks the object to say how many  elements it could possibly hold,  of the
     type you've told it.   In this case,  we'll be using an element for every
     line,  so line 46 tells us how many  lines  our  program  is  capable  of
     accessing.

     Line  47  asks the object to set up the array as LARGE as it can possibly
     be made.   Note that this will consume ALL DOS memory.  If you were using
     more than one of these objects, you'd probably not pass it the maximum.

     Line 55 is where things start  to get interesting.   One of BigDOSArray's
     methods is called Elem.   If you pass it the  element  number,   it  will
     return with a pointer to where that element is stored in memory.  So Elem
     gets passed the line number,  and LinePtr then points to where we put the
     offset.  Line 56 shows how the array gets its value.

     Because LinePtr holds the element's address, we could just as easily read
     from  LinePtr^ as written to it,  and that's what happens in lines 73 and
     74.  We find the offset of the line we want, we seek to the position, and
     we read the file, just like we did in MDP5.PAS.

     One last point - note the calls  to  the Done method of LineBank in lines
     66 and 86.  This hands back the memory the array consumed back to DOS.

     We now have the shell of a fairly powerful program.   On my machine,   it
     says it's happy to handle over 130,000 lines (60,000 in the TP IDE).  The
     fidonet  nodelist  (052)  is 16302 lines long,  and in a few weeks,  Vern
     Buerg's LIST program isn't going  to  be  able to handle that.   At least
     you've now got something that can!

     If you've understood everything so far,  then relax - from now on, things
     are all down hill.  MDP7.PAS is just MDP6.PAS with the scrolling stuff we
     worked out in MDP4.PAS.   It covers nothing new,   just  merges  the  two
     techniques we developed before.

     MDP8.PAS is as far as we're going to take things this issue.  Still, it's
     quite  a  program!    It's  been split up into separate procedures (as it
     should have right from the  beginning!),   and  a number of features have
     been added.  First, it shows you a percentage as you load the file.  This
     is useful when you're loading the FidoNet nodelist,  or an online version
     of the Bible [one of the best reasons to download it -  it  gives  you  a
     great exerciser for your text-processing programs!  ;-D]

     Also, it now responds to extended keys, such as the arrows, and the PgUp/
     PgDn/Home/End keys.  ESC has replaced 'q' for exitting, and you can press
     # to jump to a certain line.   This is accomplished by having TWO CASEs -
     one handles extended keys, and the other, normal files.

     I'm  the  first to admit that it's not the fastest thing around.   That's
     why in the next issue of PNL we'll discuss optimising it.   We'll use the
     profiler to investigate  where  the  program  is  spending  the most time
     (everywhere,  ie,  equally slow!).   We'll rewrite a large chunk of it in
     assembler, and we'll use multitasking to remove the annoying delay at the
     start of the program.

     Also,  we'll discuss how we'd port the program to TurboVision, and how to
     have it run RIGHT under DesqView.   Finally,  we'll see what  we  can  do
     about  viewing  lines  that  are  longer  than  the  screen by horizontal
     scrolling.
      PNL #10                      Page 9                         March, 1992




     Until next issue,

     Make merry with your Pascal!

        +-------------------------------------------+
        | Mitch Davis                               |
        | 10/1072 Whitehorse Rd.,                   |
        | Box Hill, Victoria, Australia.  3128.     |
        |                                           |
        | Ph. +61-3-890-2062.  Fidonet: 3:634/384.6 |
        +-------------------------------------------+
     PNL #10                      Page 10                         March, 1992



     =================================================================
               BigArray - you've never seen arrays this BIG!
     =================================================================

     *** Error 22: Structure too large.

     *** Error 49: Data segment too large.

     *** Error 96: Too many variables.

     *** Runtime error 201 at XXXX:YYYY. {Range check}

     *** Runtime error 203 at XXXX:YYYY. {Heap full}

     How many times have you  been  frustrated by these little beauties?   How
     many times has it been because you had an array that just  wouldn't  stay
     down?   There are two ways to fix this - you could put your thinking on a
     diet  and  work  out  some  other  way that doesn't consume quite so much
     memory (recommended!) or you could do it the lazy way, and slot in a unit
     called the (drum roll!) *** BigArray ***.

     BigArray will give  you  arrays  which  can  be  as  big  as  will fit in
     conventional memory,  ie, they aren't limited to 64k.   The arrays can be
     of any type, and are of a single dimension.  (A version being written now
     lets you have arrays that spill over to disk or EMS,  as  well  as  multi
     dimensionals.)

     The  large  arrays are implemented as objects.   You use the .SetElemSize
     method to tell it how  big  your  elements are,  and you can subsequently
     find out via .GetMaxSize how many elements  the  largest  array  can  be.
     Then you call the Init method with the number of elements you'd like, and
     voila, your array is done!  When you're finished with the array, call the
     .Done method.

     How  are elements accessed?   You pass the element subscript to the .Elem
     function, and it returns a pointer to that element.  The idea is that you
     then assign  that  pointer  to  a  pointer  variable  of  the type you're
     storing.   Then by deferencing the pointer (following it by a caret  [^])
     you  can  then  access any fields,  bits,  WHATEVER that you would with a
     plain-jane variable.

     Let's look at a sample of how you'd use them:

     program BigArrayTest;

     {Program to accompany article in issue #10 of the Pascal NewsLetter.     }
     {Author: Mitch Davis, (3:634/384.6) +61-3-890-2062.                      }

     {$G+,M 16384,0,0} {Make SURE you tell TP not to steal all the DOS memory }
                       {for a heap!  If you're not using dynamic variables,   }
                       {set both numbers to 0.                                }

     {This program provides a nonsense demonstration of how you use the tools }
     {in the BigArray unit.                                                   }

     uses Test186, BigArray; {I run my programs on a i286.  If you have an XT,}
                             {Remove the "G+," from above and the "Test186"   }
                             {from the uses section.                          }
     PNL #10                      Page 11                         March, 1992




     type PigeonType = record
                         value:real;
                         changed:boolean;
                       end;

     var PigeonHole:BigDosArray;
         PigeonPtr:^PigeonType;
         PHnum, MaxSize:longint;
         GlobalChanged:boolean;

     begin
       writeln ('Welcome to the pigeon-hole.');
       with PigeonHole do begin {This sets up the big array.}
         SetElemSize (sizeof (PigeonType));
           {Tells it how big each element will be}
         MaxSize := GetMaxSize;
         Init (MaxSize); {Make it as big as possible.
                         This is not compulsory.    }
       end;
       writeln ('There are ',MaxSize,' pigeon-holes, numbered from 1 to '
        ,MaxSize,'.');
       write ('Please wait while I clear them... ');
       for PHnum := 1 to MaxSize do begin
         PigeonPtr := PigeonHole.Elem (PHnum); {Get the address of the element}
         PigeonPtr^.Changed := false; {Reference the changed field within that}
       end;                           {element.                               }
       writeln ('Done.');
       GlobalChanged := false;
        {This will save us search time later if no changes}
       repeat
         write ('Which pigeon hole? (1-',MaxSize,', 0 to quit): ');
         readln (PHnum);
         if (PHnum > 0) and (PHnum <= MaxSize) then begin
           PigeonPtr := PigeonHole.Elem (PHnum);
           with PigeonPtr^ do begin
             case Changed of
               true :writeln ('That pigeon-hole has the value of ',Value:3:2);
               false:writeln ('That is a new pigeon hole.');
             end;
             write ('Change it to? ');
             readln (Value);
             Changed := true; {means this ph will be shown in the end summary}
           end;
           writeln ('Pigeon-hole changed.');
           GlobalChanged := true;
         end;
       until PHnum = 0;
       writeln ('-------------------------------------------------');
       case GlobalChanged of
         false:writeln ('You didn''t change any pigeon holes.');
         true :begin
                 writeln ('The pigeon holes you changed were:');
                 write ('Wait..',#13);
                 for PHnum := 1 to MaxSize do begin {scan thru the ph's}
                   PigeonPtr := PigeonHole.Elem (PHnum);
                   with PigeonPtr^ do
                     if changed then writeln (PHnum,': ',value:3:5);
     PNL #10                      Page 12                         March, 1992



                 end;
               end;
       end;
       writeln ('Thanks for using the pigeon-holes!');
       PigeonHole.Done;
     end.

     If you understand this  (in  conjunction  with the "interface" section of
     the BigArray unit),  then you should have no problem working out  how  to
     use it.

     A few usage notes:

        o   The   Big   Arrays   are   1-based,    that   is   if   you  call
          BigDOSArray.Init(10),  then the element subscripts run from 1 up to
          10.

        o There is NO checking  to  ensure  that  the  element  you  pass  to
          BigDOSArray.Elem is within the defined range.   If it isn't,  then
          you'll get garbage results for the pointer result.  Beware!

        o The  code for BigDOSArray hasn't been extensively tested.   Caveat
          Emptor!

        o BigDOSArray  needs  optimising  BADLY!    I  plan  to  write  it in
          optimised assembler throughout;  in the meantime,  you'll  have  to
          sacrifice speed for convenience.

        o  I  am  aware  that Turbo Power markets code which implements large
          arrays (and probably  MUCH  better  than  I've  managed here!).   I
          haven't looked at their code.   Trevor Carlsen's OverSize unit also
          performs a similar function.   I looked at his code,  and  couldn't
          understand  it.   That's not meant as a slur on Trevor (3:690/644),
          who writes code so good it  should  be framed.   I just thought I'd
          take a different approach.  Upshot: If any of the other large-array
          products fit your bill better, use them!

        o The BigDOSArray unit currently compiles in i286 mode.   If you have
          an XT or similar, chop out the G+ and Test186 bits, in the same way
          as the comments in the demo program mention.

     Well  that  about  wraps  it  up.    I'd be more than happy to answer any
     queries you have about the unit.   Next issue, time and space permitting,
     I'm planning on having a  followup  article which goes into optimisation,
     multi-dimensional arrays, etc.  Till then?  Make merry with your Pascal!

     +-------------------------------------------+
     | Mitch Davis                               |
     | 10/1072 Whitehorse Rd.,                   |
     | Box Hill, Victoria, Australia.  3128.     |
     |                                           |
     | Ph. +61-3-890-2062.  Fidonet: 3:634/384.6 |
     +-------------------------------------------+
     PNL #10                      Page 13                         March, 1992



     =================================================================
           High Quality Sound in Turbo Pascal... Not Really.
     =================================================================

     Many Turbo Pascal programmers find that  the  SOUND  procedure  does  not
     produce  anything  interesting as far as music goes.   This is not due to
     Turbo Pascal's limitation  to  use  the  speaker  but  due to the speaker
     itself.   The original PC speaker is almost useless...  Unless you happen
     to have the public-domain utility called RESPLAY,  written by Mark J. Cox
     and (of course) Turbo Pascal.

     --- From RESPLAY v1.0 documentation: ---

     "RESPLAY is  a  memory  resident  program  designed  to  help  high-level
     language programmers make the most of the PCs useless speaker.  By use of
     some simple procedures, you can playback digital samples from within your
     programs  and sample yourself.   Playback can either be to the PC speaker
     (no too bad) or to some external hardware [...] "

      Possible uses of RESPLAY:

       o Arcade Games ("He's Dead Jim!" or realistic shooting noises)
       o Sound Analysis (Fast Fourier Transforms/ analysing spoken words)
       o Spelling Games for kids.
       o Sampling your friends and making them sound silly (by changing
         their speed, or by FFTs and altering their pitch!)
       o The next 'Jive Bunny' single

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

     Resplay is fairly easy to use from Turbo Pascal because it uses interrupt
     calls.   Simply fill the register set and call interrupt 2F.  If you have
     any knowledge of assembler, RESPLAY will work for you!

     The original archive file of RESPLAY contains a C source code that I have
     "ported" to Turbo Pascal.   I say  "ported" because the two programs work
     differently with memory (loading & storing digital samples) but they both
     do the same thing: Play a digital sample file.

     I have done much of the work for you.  I have created a Pascal unit which
     can read and play any sample.   I have used TP's INTR procedure  to  call
     the  interrupt  instead  of  direct  BASM  instructions for compatibility
     purposes.   Turbo Pascal v5.5 users will be able to use this code without
     changing one line of code.

     The unit is name DIGISND.PAS and the program is PLAYDIGI.PAS.

     I must admit that  the  program  is  not  fully optimized.   It reads the
     sample file by allocating 64k memory segments and  plays  these  segments
     one after the other...  I have played samples up to 350k with it.  If the
     sample  is  bigger  than  64k,  you will notice some delay (under 10 ms.)
     between each 64k "chunck".   It is  possible  to tell RESPLAY to play the
     whole sample if the sample is stored continuously  in  memory.    I  have
     decided to play the game safely and play the samples by 64k segments.

     The code is object-oriented so that anyone wishing to modify it may do so
     by creating a descendant object type.  Someone might want to add a method
     to play only part of the sample read (eg.   from time x to time y),  play
     PNL #10                      Page 14                         March, 1992



     the sample on a COVOX card instead of the PC speaker, etc...

     Please refer to RESPLAY1.ZIP before modifying the unit.

     [Editor's  Note:   RESPLAY1.ZIP  has  been  included  in the distribution
     archive of PNL#10.   It contains the original documentation and a digital
     sample.]

     By the way, I have tested RESPLAY with my program on a COVOX card and the
     resulting "music" is better than  with  a SoundBlaster card.   Also,  try
     getting Macintosh (Yes,  Macintosh!) digital samples, they work just fine
     with RESPLAY.

     Hope you have fun with your PC speaker now!

     Alex Boisvert
     DataMAX Communications Enr.
     FidoNet @ 1:167/405
     PNL #10                      Page 15                         March, 1992



     =================================================================
             Overlays - How to fit your programs in memory
     =================================================================

     Overlays in Turbo Pascal help you, the programmer, to write programs that
     otherwise would not fit into the 640k limitation of DOS.   They can also,
     in special cases,  reduce your code's memory requierement in order to get
     more memory for dynamic allocations.

     To accomplish this, overlays are "parts" of your programs that are stored
     on disk and which are controlled  by  the program,  meaning that they are
     loaded in memory only when they are needed.  The principle is to allocate
     the same (limited) memory space to several sections  of  program...    at
     different time obviously.

     The  major drawback of using overlays is the time involved in loading the
     useful sections at run time,   or  "on-the-fly".   You will therefore use
     this method only when available memory is less than your program's  size.
     Your  programs  will then become independent of RAM memory available,  to
     which the computer has access to.

     The developpers' of Turbo Pascal have kept to a minimum the rules to obey
     when using the  overlays.    Management  of  overlays  is  handled by the
     routines of the OVERLAY unit,  which you will have to add  to  your  USES
     statement at the beginning of your main program.

     The  smallest  portion  of code which can be declared as "overlayed" is a
     unit.   With  Turbo  Pascal,   it  is  not  possible  to overlay specific
     routines.   Each unit is loaded in the heap memory zone,   which  is  the
     reserved memory between the stack and the memory of dynamically allocated
     variables.

     When using overlays, you must declare all your program's routines as FAR.
     The easiest way to do this is to add the following compiler directive

       {$F+},

     just after your program's header.  Any unit compied with the directive

       {$O+},

     can then be compiled as an overlayed unit, but they do not have to.  This
     directive  will  tell  the compiler to produce the needed overlay control
     code.

     The following compiler directive

       {$O UNIT_NAME}

     will indicate if a unit specified in the USES statement will, in fact, be
     overlayed.    It  is  therefore   possible   to  use  this  unit  without
     "overlaying" it simply by using the USES statement and ommiting  the  {$O
     UNIT_NAME} directive.  This unit would then be treated "normally", as any
     other unit declared by the USES statement.

     This  represents  an  alternative.    You  can overlay the unit or not...
     depending on the program using it.
     PNL #10                      Page 16                         March, 1992



     When you use the overlay technique,  you have to specify the OVERLAY unit
     in the USES statement and, initialize overlay management by calling the

       OVRINIT('filename.OVR'),

     routine.   It is best  if  this  is  done  right  at the beginning of the
     program.  Here is an example:

        PROGRAM myprogram;
        {$F+}   {forces FAR calls}
        USES
          OVERLAY, CRT,  {TP 6 units}
          myunit1, myunit2, myunit3;   {your own units}

        {$O myunit1}
        {$O myunit2}  {Overlay these two units ... }
                      {Note that "myunit3" is overlayed.}

        {...}
        {declaration goes here}
        {...}

        BEGIN {main program}
          OVRINIT('MYPROG.OVR');
          {...}
          {other commands here}
          {...}
        END.

     In this case,  the compiler creates two files: MYPROG.EXE, the executable
     file, and MYPROG.OVR, the overlay file.

     Now,  the following  example  will  demonstrate  the  basics  of  overlay
     management.   The program will integrate two units,  OVER1 and OVER2, and
     call  two  routines  that  they  contain:  HELLO1 and HELLO2.   These two
     procedures will simply display a message.

     Here are the two overlays:

        Unit OVER1;  {OVER1.PAS}
        {$F+,O+}

        Interface
          procedure HELLO1;

        Implementation
        uses
          crt;  {TP unit}

        procedure HELLO1;
        begin
          writeln('Hello from unit OVER1.');
          delay(1000);
        end;

        end. {end of unit OVER1}
     PNL #10                      Page 17                         March, 1992



        Unit OVER2;  {OVER2.PAS}
        {$F+,O+}

        Interface
          procedure HELLO2;

        Implementation
        uses
          crt;  {TP unit}

        procedure HELLO2;
        begin
          writeln('Hello from unit OVER2.');
          delay(1000);
        end;

        end. {end of unit OVER2}

     Now,  compile the sources of the two overlays.   Once this is done,  your
     disk will contain two source files OVER1.PAS and OVER2.PAS and also their
     corresponding compiled TPU unit OVER1.TPU  and OVER2.TPU.   Do not forget
     to select the option "Compile/Destination" to Disk,  otherwise,  the  two
     TPU files will not be created.

     The main program looks like this:

        program OverlayDemo; {OVRDEMO.PAS}
        {$F+}

        uses
          overlay, crt,    {TP units}
          over1, over2;    {overlays}

        {$O over1}
        {$O over2}

        var
          i: integer; {loop counter}

        begin  {main program}
          ovrinit('ovrdemo.ovr');  {initialization}
          for i:= 1 to 10 do
          begin
            clrscr;
            Hello1;  {call routine in unit OVER1}
            Hello2;  {call routine in unit OVER2}
          end;
          write('Program is finished.  Please press [ENTER].');
          readln;
        end. {program}

     The  two  units  merged  together  to  form  the overlay OVRDEMO.OVR when
     compiling the  program.    Compiling  will  also  (obviously)  create the
     executable OVRDEMO.EXE.

     The overlay file  must  always  reside  in  the  same  directory  as  the
     executable  file but it is also possible to merge the overlay file to the
     .EXE file with the command
     PNL #10                      Page 18                         March, 1992




       COPY /B ovrdemo.EXE + ovrdemo.ovr

     which, in fact, appends the overlay at the end of the .EXE file.  In this
     case,  the command  OvrInit('ovrdemo.EXE')  will  initialize the overlays
     integrated in the .EXE file.

     As you have seen by now,  the overlays are not  difficult  to  implement.
     Just  a  few modifications around the USES statements and the addition of
     the compiler directives {$O ...} and {$F+} can lead to a program which is
     less RAM memory dependent.   You  may  wish  to consult your Turbo Pascal
     manuals for further informations  on  using  EMS  memory  with  overlays,
     clearing the overlay buffer (increasing dynamically-"allocatable" memory)
     and modifying its size.

     I  have  used  overlays largely with the Object Professional Library from
     Turbo  Power  because  object-oriented  programs  tend  to  create larger
     programs because  the  compiler  cannot  easily  "strip"  unused  virtual
     objects out of the .EXE file.

     For  obviously  reasons,  another drawback of using overlays is that your
     program cannot be compressed by  a  program  like PKLITE or LZEXE,  which
     dynamically uncompress your program in memory when executed.  The overlay
     file has to stay in its  original  uncompressed form on disk because this
     is how your .EXE file expects to read it.

     Alex Boisvert
     DataMAX Communications Enr.
     FidoNet @ 167/405
     PNL #10                      Page 19                         March, 1992



     =================================================================
                   Turbo Vision without getting GUI.
                  Copyright (c)1991 Richard A. Morris
     =================================================================


     INTRODUCTION

        Around November of 1990, as most of us know, Borland released Turbo
     Pascal 6.0, and with their add-in toolbox Turbo Vision unveiled a new
     Programming Methodology for Turbo Pascal called Event Driven Object
     Oriented Programming.

     Well, did the Fewmets hit the Windmill or What !?!

     Erstwhile conservative commercial programmers ranted that TV was every
     thing from a Goblin to a toy toolbox destined to be unsupported by
     Borland, they complained that it consisted merely of cute saccharine
     windows,  of little or no practical use in professional projects.  The
     silence from a lot of Third-Party Code Shops, who had always provided
     outstanding and timely Developer support packages, was deafening as they
     waited to see how the programming community would react (Blaise being the
     only exception).  In a classic Catch-22, Programmers have been warily
     watching Borland to see if they will support Turbo Vision, Meanwhile
     Borland, and third-party Code shops, quietly had their eyes on the Turbo
     Pascal community to see if TV was to become popular enough to support,
     potentially making the bitching of disgruntled users a self-fulfilling
     prophesy.

     It's now a year on, slowly but surely Programming journals, and Reference
     Books  are  starting to constructively cover the complexities of TV (Most
     notably Messrs.  Rubenking and Duntemann, thank you sirs).  OK so why do
     so many good Pascal programmers have so much dirt to throw at TV,  surely
     they must know what  they're  talking  about?    Well  TV is a not merely
     another toolbox full of useful  functions,   but  rather  an  application
     framework  that  impertinently  confronts  the  programmer  to completely
     rethink his/her programming design,  from scratch.   It's a big ask for a
     lot of us who have several years of training invested in our trade.  Most
     of us looked at the Demos,  and  thought 'Well I'm certainly not going to
     put my name on such a "Mickey-Mouse" program.   Wonder if I can  make  it
     look  a bit more professional?',  had a look at the code and found it too
     difficult to personalize the interface,   and  so  gave up any attempt at
     using it, and concentrated  on  our  own  custom  interfaces  that  we'd
     invested so much skull sweat upon.

     Two  programmers I have much respect for,  convinced me by their comments
     to take a second look at TV.   Is it worth worrying about then, well in a
     my native vernacular, 'Bloody Oath'!   I must add that it took me several
     months of painful  re-education  to  bend  my  Object Oriented programing
     abilities around TV.   Turbo Vision is certainly no beginners toolbox  as
     it  would  appear from Borland press releases and manuals,  this may have
     something to do with the sluggish move of programmers to using it.

     Firstly any prospective programmer  must  firmly  understand the usage of
     Pointers and heap memory,  Next Object Oriented principles must be almost
     second  nature,   Finally  the  Event  Driven  paradigm  should  be  well
     understood.   Them's three steep learning curves!   The next  problem  as
     outlined  by  Jeff  Duntemann  in  Dr  Dobbs  Journal  Nov,   1991 in his
     PNL #10                      Page 20                         March, 1992



     "Structured Programming" column (Read it!), is that there no "Front Door"
     to start off your Research,  that you must digest Turbo Vision in a whole
     gestalt.

     I plan to do some articles on my experience of coming to grips with Turbo
     Vision in the next few issues of PNL, however today I'll start with a few
     nice little goodies that come with TV, which can be used independently of
     the Application Framework.   This allows us to at least deal with some of
     the TV topics without haveing to  see  the "Big Picture" at once.   Let's
     discuss  TV's  Objects  Unit,   which  contains   among   other   goodies
     Collections, and Stream.  You should have a fair grasp of Object oriented
     programming,   and  naturally  a  good concept of dynamic memory and heap
     usage.


     INTERESTING OBJECTS

     Lurking in the set of Turbo Vision  Units that comes free with every copy
     of Turbo Pascal 6.0 is the Unit "Objects.TPU".   It's well worth  looking
     into  this  Unit  to  see the usefull routines and objects that the Turbo
     Vision User interface uses as  it's  glue to bind itself together.   I'll
     leave Collections and Streams for last.

     Open the file Objects.Int (In your DOC subdirectory of a  standard  turbo
     pascal setup), and open your Turbo Vision Guide to page 190.  Refer often
     to  the  Global reference from page 327 as we talk of Types,  procedures,
     and functions,  and the  Object  Reference  from  page  205 as we talk of
     Objects.


     TYPE CONVERSION TYPES

     One of the main advantages and disadvantages of Pascal  has  always  been
     the  strong typing.   This means that if you have a variable of a certain
     type you can't usually  assign  it's  contents  to  a variable of another
     type.   The advantage is that it saves the unwary  programmer  writing  a
     large  variable  over a smaller variable and corrupting the memory of all
     variables  following.    The  disadvantage  is  that  it  constrains  the
     programmer from passing data between dissimilar types.

     Turbo pascal changed all this by allowing you to typecast between any two
     types of the same size,  for example  a  Pointer and a longint are both 4
     bytes long so we can do the following;

     Var
       Ptr : Pointer;
       Long: Longint;
     begin
       Long := Longint(Ptr);
     end;

     And the Long variable will contain a copy of the bytes of information  in
     the  Pointer  variable,   this  may not sound particularly usefull.   But
     consider the WordRec,  it allows  you  to  extract the high and low Bytes
     from a word, Visualise this example for Colours.

     Var
       MyColour : Word;
     PNL #10                      Page 21                         March, 1992



       MyBackground : Byte;
     Const
       Black = 0; White = 7;
     begin
       WordRec(MyColour).Hi := Black;
       WordRec(MyColour).Lo := White;
       MyBackground := WordRec(MyColour).Hi;
     end;

     Then we have the Pointer array types which  can  be  type  cast  over  a
     pointer to access the pointers contents as an array of Bytes,  or Words.
     Remember you can typecast types of the same size,  and all pointers are 4
     bytes in size regardless of the size of the block they point to.   So  in
     the following you can access the ith byte of a dynamic variable.

     Var
       BlockPtr : Pointer;
       Index    : Word;
     begin
       GetMem(BlockPtr,2000);
       For Index := 1 to 1000 do
         pWordArray(BlockPtr)^[Index] := Index;
     end;

     This  allows you to allocate a block of 1000 words (2 bytes each in size)
     numbered from 1 to 1000, each initialised with their index number.


     STRING POINTERS

     The  PString type,  the NewStr Function and the DisposeStr procedure are
     usefull items,  they are used  throughout TurboVision and they allow you
     to allocate and dispose of dynamic Strings easilly.  You simply pass the
     newstring function a String variable and it will return a Pointer  to  a
     copy of that string stored in Dynamic Memory.   You pass that pointer to
     DisposeStr  and it will return the Dynamic Memory used back to available
     heap memory.   Two nice tricks are that NewStr('') returns a nil pointer
     wasting no Space,  and dispose doesn't abort with an error if you dispose
     a nil pString.

     Sure you could implement  this  stuff  yourself,   and  most of us have.
     However it is all done  in  Objects,   and  it links in well with usefull
     routines like the String Collection.

     COLLECTIONS

     Often as programmers we want to be able to store a collection  of  items,
     we  can  use  an  array,   the  limitation  being  that  the size must be
     determined at compile time,  and can't change,  and can't be greater than
     64k.   The other option is  to  create  a linked list structure,  this is
     middling difficult to create,  but almost impossible to debug if you make
     mistakes,  and accessing an item  requires  traversal  through  preceding
     items, hardly optimised.

     Turbo  vision introduces an Object called (AHA!) a Collection.   It is in
     fact a  object  encapsulate  mutated  resizing  index  array  that allows
     indexed access like an array, with the resizability of a linked list, and
     because it indexes pointers to your objects you  can  store  any  dynamic
     PNL #10                      Page 22                         March, 1992



     variable or Object.

     It's  use  is  relatively  simple,   you  simply  create  a descendant of
     TCollection and overwrite it's  freeItem  method,  to tell the collection
     mechanisms how to get rid of an Item's memory,  so that for example  when
     you dispose of the whole collection it knows how to dispose of the memory
     for each item in the collection.

     Initialise  the  Collection  with two words,  and Insert pointers to your
     items already stored on the heap.   The two variables are used internally
     by the collection when building  the  index table,  the first variable is
     your best guess as to the starting number of items you will store and the
     second is the amount you want the table to grow by each time you add more
     variables than the current index can hold.  I generally use 10 and 10, if
     I don't know the nature of the collection.   Have a look at StrList for a
     simple example of how to store a collection of strings.

     If you look at my main program loop it  initialises  a  dynamic  variable
     pointing to a my new collection type, it then uses the newStr function to
     store  a  string  constant  in  the heap,  and uses the collection method
     insert(p :  Pointer)  to  insert  the  pointer  to  that  string into the
     collection.  It's a shorhand method that could be represented in longhand
     as follows;

       StrVar := 'Four';
       StrPtr := NewStr(StrVar);
       MyStringList^.Insert(StrPtr);

     Then my main loop calls a new method I created  for  this  object  called
     Print_Sentence,    which  then  calls  a  method  in  Tcollection  called
     ForEach(Action :  Pointer),  this calls a far local (Important can not be
     global) procedure pointer (PrintWord)  for  each  Item in the collection.
     Finally I dispose of my special  collection  using  the  TCollection.Done
     destructor, which calls FreeAll, which in turn Deletes the index item and
     calls  freeItem  for Each Item.   Which is why we overwrote FreeItem,  to
     show our collection how to dispose of an item.

     I Cheated a bit,  there is actually a StringCollection Object provided in
     Objects,  that already knows all about pStrings,  and sorts them into the
     bargain, but I thought as an example it would be good to show a little of
     the mechanism of creating a specialised collection type.

     But there is much more  to  collections,  the standard freeItem method is
     not just abstract,  it actually typecasts an object of type TObject  over
     the pointer to your Item,  and calls IT's virtual destructor.   Why, well
     all turbo vision objects are in fact descended from a TObject, and if you
     had inserted a TV object (Or any of your own objects descended ultimately
     from  a  TObject),   a  vanilla  TCollection  would know all about how to
     dispose of  such  an  item,   and  we  would  not  need  to  descend from
     TCollection and override TCollection.FreeItem.

     Unfortunately a pstring is not an object and thus can never be  descended
     from TObject,  however a TCollection IS descended from TObject,  and thus
     you could have a Collection of Collections, or (and this is the good bit)
     a  collection  of  various objects,  as long as each was descended from a
     TObject.

     I show an example of  a  Collection  of  Collections in the included file
     PNL #10                      Page 23                         March, 1992



     ReadIni.Pas, which is a simple unit to read a Windows type INI File.   It
     reads a Text file (See example Test.INI)  and  creates  a  collection  of
     TagCollections, each being a collection of parameters, ie:

        TAGCollection = Object(tCollection)
          |
          +- PARAMCollection = Object(tCollection)
               TAG : PString
               |
               +- PARAMItem = Object(TObject)
                    Param : pString
                    Vars  : pString;

     As  the  paramItem  is  a  descendant of TObject,  no special hadling is
     required  to add it to a PARAMcollection,  which has it's own TAG string,
     then all PARAMCOllections  (One  for  each  GROUP  TAG)  are  stored in a
     TAGCollection.

     There is sparse documentation in the Source of READINI,  To fully explain
     the code here would bulk  up  this  article,   so  I'll  leave  it  as  a
     demonstration  of  what  you  can do with collections,  and it could be a
     usefull unit as it stands without you modifying anything.

     STRINGLISTS

     The String List is another usefull object  in the OBJECTS unit.   It is a
     way of collecting a bunch of strings indexed by words.   The way it works
     is this,  you create a program to make a StringList using a tStrListMaker
     Object,  which once Constructed with an INIT,  simply requires you to use
     tStrListMaker.Put(Index,TextString) which adds TextString to  the  Object
     indexed by INDEX.  You then save it to a file (More about this in Streams
     and Resources).   Now you create another program that reads a tStringList
     from  this File,  then you simply ask the object for the text string that
     corresponds to a particular index number.

     The procedure is  simple,   I  have  included  the sample files Make_Err,
     Err_Desc,  and Test to describe the procedure.   You'll want to  read  on
     about  streams  and  resources as they are used in these files.   However
     I'll describe a few uses for String  Lists.   In the example I have given
     Make_Err simply puts a Text String detailing  each  Runtime  error,   and
     stores  the  description indexed with the run time error code.   Desc_Err
     simply  finds  out  the  exitcode  and   if  non-Zero  it  prints  out  a
     description.   So what you say,  well let's say for example that you have
     to create a version of your program for a swedish audience.   You  simply
     create a new ERRORS.STM file to put with your program, and you don't need
     to change and recompile your code.

     The  REAL  advantage  is  that now that the Europeans are finally getting
     their act together with a common  economic  bloc,  there will be a lot of
     demand for programs that are multi lingual.   If you use a stringList for
     ALL text strings in your code,  you'll be able to have users select  upon
     startup  the  language  they  wish  to converse with the program in,  and
     simply activate THAT string List.

     As a favour to  me  could  those  of  you  who  are fluent in Non-English
     languages,  send in to  the  Newsletter  a  modified  Make_Err  for  your
     language,  we'll compile them all together and distribute a multi lingual
     Runtime error descriptor.
     PNL #10                      Page 24                         March, 1992




     STREAMS AND RESOURCES

     About  the  most  sophisticated  file  handling  in Turbo pascal up until
     TP5.0,  was the  TEXT  file  type.    With  the  TP5.0 demo files Borland
     introduced a sophisticated Object called a  Stream  that  allows  you  to
     easilly  perform  polymorphic  Object  File  Input/Output.    Turbo power
     extended this with their own  stream objects in Object Professional,  and
     finally we have it  integrated  in  Turbo  Vision.    There  is  a  great
     discussion  on  Streams  in  Chapter  8 of the TV Guide,  I shan't try to
     repeat this here,  however I'll  give  a  general  run down on the use of
     Streams, and their application in the real world.  I do plan to do a more
     detailed tretise on Streams in the future (given time) based on a unit  I
     use  called  NetStrm,   which  extends  the  buffered  stream  to work on
     Networks.

     How does it work,  well  most  turbo  vision objects (And any Objects you
     want to use with Streams) have two extra methods,  a constructor  usually
     called Load, and a procedure called Store, which each take as a parameter
     a  stream  variable,   and  do  pretty  much what they say they do.   The
     trickiness is that  when  you  prepare  to  use  objects in streams,  you
     register the Objects you will use.

     Registering an Object entails sending information  to  the  Objects  unit
     information  so  that  it knows where to find your objects Load and Store
     methods,  a unique word called an ObjType, and the VMT link (Which within
     OOPS Pascal uniquely defines  an  Object  Type)  which allows the Objects
     unit to know the size of the dynamic object it must create during a  load
     construction.

     When an object is stored to a file, it's ObjType number is first saved to
     the Stream,  then the Store method is called to put it's information onto
     the file.   Ok So what is the VMTLink for, well when you tell a stream to
     load an object,  it reads the OBJType number,  then searches it's list of
     registered  objects  and uses the VMTlink that it finds to do all the OOP
     trickiness to construct the Object,   then  uses  the pointer to the Load
     constructor  registered  with  the  object,   to  fill  the  object  with
     information from the file.

     So What?   Well what this means is that your stream is asked  to  get  an
     object,  it will return to you a pointer to an object, after constructing
     it for you, WITHOUT KNOWING WHAT THE OBJECT IS.  Ha, So what again.  Well
     it  means  that you can store any registered descendant of TObject with a
     store and complimentary Load mechanism,   and  load it back again without
     having to know at compile time the information you  are  sending  to  the
     screen.

     Resources are simply streams with an index comprising of key strings,  in
     the  case  of  Make-Err and Desc_err I store the stringlist to a resource
     under the keystring "ERRORDESC".

     As an example of the use of Streams, Say you have 5 or 6 different record
     types that have to be input and  output  to a file.   In the 'Olden days'
     you would use the case command to create  a  variant  record,   and  each
     record you stored would be the size of the LARGEST record case.  This way
     you  simply declare a different Object for each record type,  and you use
     only as much space as each record  needs.   Furthermore if you have put a
     method called EditRec into an abstract Object,  and  descended  all  your
     PNL #10                      Page 25                         March, 1992



     record  objects  from  it,   and in each case overriden Editrec to do the
     individual record  editing  and  manipulation  you  could  simply  do the
     following;

      Incognito := TStream.Get;
      Incognito^.EditRec;

     It's a lot easier if your object is  a  Turbo  Vision  View  with  record
     information,   and editing dialogs,  but then you'd have to use the Turbo
     Vision User Interface.  Ah well, we'll all be using it sooner or later.

     Refference sources

             Turbo Vision Guide
             Borland Intl

             "Structured Programming"
             Dr. DOBBS journal
             November 1991

     About the author

             Richard  Morris  is  the  CEO of KHIRON Software,  a Queensland,
             Australia based Computer  consultancy,   that has been providing
             contract programmers,  and computer support to small  to  medium
             business' sin 1985.

             Richard can be contacted via the following
             Fidonet:  3:640/372.5
             IntlNet:  58:1100/378
             Voice:    (07) 812-3218
             Post:     C/- KHIRON Software
             P.O. Box 544,
             INDOOROOPILLY  Qld  4068.
     PNL #10                      Page 26                         March, 1992



     =================================================================
                FREQUENTLY ASKED QUESTIONS IN THE PASCAL ECHO
     =================================================================

     Q1. How do I pass an error level code when my program finishes?

     A1. The halt procedure takes an optional parameter of type word.  Thus -

         halt(1);

         terminates the program with an errorlevel of 1. If halt is used
         without a parameter it is the same as -

         halt(0);

         Note:  When a program is terminated using the  halt  procedure  any
                exit procedure that has previously been set up is executed.


     Q2. How do I empty the keyboard buffer?

     A2. There are several ways  that  this  can be achieved.   However the
         safest is -

            while Keypressed do ch := ReadKey;

         This requires that a variable ch of type char is  declared  and  the
         crt unit be used.  To do it without using a variable -

           while Keypressed do while ReadKey = #0 do;

         or if using TP6 with extended syntax enabled -

            while KeyPressed do ReadKey;

         If  you  do not wish to incur the substantial overhead involved with
         the use of the CRT unit and  there is no requirement for the program
         to run under a multi-tasker -

            var
              head : byte absolute $40:$1c;
              tail : byte absolute $40:$1e;

            tail := head;


     Q3.  When I redirect the screen output of my programs to a file the file
         is empty and the output still appears on the  screen.    What  am  I
         doing wrong?


     A3.    You  are  probably  using  the CRT unit and its default method of
         writing to stdout is by  direct  screen writes.   In order to enable
         output to be redirected all writes must be done by DOS.  Setting the
         variable DirectVideo to false has no effect on redirection as all it
         does is use the BIOS for screen writes - not DOS.

        
     PNL #10                      Page 27                         March, 1992



         To enable redirection you must not use the CRT unit

         OR

         assign(output,'');
         rewrite(output);

         This will make all output go  through DOS and thus can be redirected
         if desired.  To restore the default situation -

         AssignCRT(output); rewrite(output);


     Q4. How do I make a string that is lower or mixed case all uppercase?

     A4. There are several ways to convert lower case characters  to  upper
         case.  Here are some of them.

          As a procedure (excluding asm code this is the fastest way)


            procedure StrUpper(var st: string);
              var x : byte;
              begin
                for x := 1 to length(st) do
                  st[x] := UpCase(st[x]);
              end;

          As a function (slower but sometimes more convenient) -

            function StrUpper(st: string): string;
              var x : byte;
              begin
                StrUpper[0] := st[0];
                for x := 1 to length(st) do
                  StrUpper[x] := UpCase(st[x]);
              end;

          Both  the  above  are suitable for the English language .   However
          from version 4.0 onwards,  DOS has had the facility to do this in a
          way that is country (language) specific.   I am indebted to Norbert
          Igl for the basic routine.  I have modified his code slightly.  For
          the anti-goto purists this  is  a  good  example  of a goto that is
          convenient,  efficient,  self-documenting and structured.   The dos
          calls would make this method the slowest of all.


         function StrUpper(s: string): string;

           { Country specific string-to-uppercase conversion. }
           { Requires DOS unit }
           label
             fail;
           var
             regs : registers;
             x    : byte;
           begin
        
     PNL #10                      Page 28                         March, 1992



             if lo(DosVersion) >= 4 then begin
               with regs do begin
                 ax := $6521;
                 ds := seg(s);
                 dx := ofs(s[1]);
                 cx := length(s);
                 msdos(regs);
                 if odd(flags) then { the attempted conversion failed so }
                   goto fail;
               end; { with }
             end { if DOS >= 4.0 } else
           fail:
               for x := 1 to length(s) do
                 s[x] := UpCase(s[x]);
             StrUpper := s;
           end; { StrUpper }


     Q5.    When  I include ANSI codes in a string and write that string to
         the screen the actual codes appear on the screen,  rather than the
         results they are supposed to achieve.

     A5.   In order for ANSI codes to be interpreted, screen writes must be
         directed through DOS and  there  must  have been a suitable driver
         loaded via the config.sys file at boot time.   All output  can  be
         directed through DOS and the driver by -

        Not using the crt unit

        OR -

        assign(output,'');
        rewrite(output);

        in which case ALL screen writes are "ANSI code sensitive"

        OR -

        You can set up write procedures that will be "ANSI code sensitive".
        (You will need an initialisation procedure to set this up.)

        var
          ansi : text;

        procedure AssignANSI(var ansifile : text);
          begin
            assign(ansifile,'CON');
            rewrite(ansifile);
          end; { AssignANSI }

        procedure WriteANSI(var st: string);
          begin
            write(ansi,st)
          end; { WriteANSI }


      
     PNL #10                      Page 29                         March, 1992



        procedure WriteLnANSI(var st: string);
          begin
            writeANSI(st);
            writeln(ansi);
          end; { WriteANSI }

        ObviousLy,   if  the  ANSI.SYS  driver  (or  an  equivalent) is not
        installed none of the above can work.

        Setting the variable DirectVideo in the  CRT unit to false will not
        achieve the desired result as this merely turns off  direct  screen
        writes and uses the BIOS for all screen output.

     Q6. When I try to shell to DOS nothing happens. What am I doing wrong?


     A6.    In  order to be able to execute any child process there must be
         sufficient memory available for  it  to load and execute.   Unless
         you advise differently at compile time,  a  Turbo  Pascal  program
         grabs  all  available  memory  for  itself when it is loaded.   To
         reserve  memory  for  a  child  process  use  the  compiler memory
         directive -

          {$M 16384,0,0)

        the default is -

          {$M 16384,0,655360}

        The first figure - StackMin  -  is  the  amount  of  memory  to  be
        allocated for the stack:

        Minimum is:    1024
        Default is:   16384
        Maximum is:   65520

        The  next  figure  - HeapMin -is the minumum amount of memory to be
        allocated for the heap.    If  there  is less memory available than
        this figure the program will not load.

        Minimum is: 0
        Default is: 0

        Maximum is:  655360  In practice it will  be  the  amount  of  free
                             memory  less the space required for the stack,
                             less the  code  space  of  the  program.   You
                             should set this to 0 unless your program  uses
                             the heap.   In that case, set it to the lowest
                             possible  figure  to  prevent  heap allocation
                             errors.   In most cases it is best to leave it
                             at  zero  and  do  error  checking  within the
                             program for sufficient  memory  at  allocation
                             time.

        The  last figure is the crucial on as regards child processes.   It
        should always be low enough to  leave  memory left over for a child
        process and high enough not to cause problems for the program  when
        allocating heap memory.
     PNL #10                      Page 30                         March, 1992





        Minimum is:  HeapMin
        Default is:  655360
        Maximum is:  655360     If   less  than  the  requested  amount  is
                                available no error is reorted.  Instead all
                                available memory is allocated for heap use.


     Q7. How do I shell to DOS?

     A7. SwapVectors;
         exec(GetEnv('COMSPEC','');
         SwapVectors;

         Read previous section on memory allocation.

         I find that it is a good  idea  to write my own Exec function which
         will do everything that is needed for me.   I  have  it  return  an
         integer value that is the DosError code.

          function Exec(p1,p2: string);
            begin
              SwapVectors;
              Dos.Exec(p1,p2);
              SwapVectors;
              Exec := DosError;
            end;

          This enables me to have a statement such as -

          ReportError(Exec(GetEnv('COMPSEC'),''));

          Now  you can have an empty ReportError procedure or you can make it
          report the error - whatever is suitable for you application.


     Q8. When I execute a child process redirection does not work. Why?


     A8. Redirection of a child process's  output only works if it is run
         under another copy of the command processor.  So -

         exec('YourProg.exe',' > nul');    will not work but

         exec(GetEnv('COMSPEC'),'/c YourProg > nul'); will work.


     Q9. How do I read an errorlevel from a child process?

     A9.   After executing a child process the errorlevel returned  can  be
         read  by  calling  the  DosExitCode function which returns a word.
         The low byte is  the  errorlevel.    A  full description is in the
         manual.

         If the command interpreter is the child  process  and  it  in  turn
         executes  a  child  process then the errorlevel of the second child
         process cannot be read without resorting to some trickery.
     PNL #10                      Page 31                         March, 1992




     Q10.   When I read a text file that has lines exceeding 255 characters
          I lose all those characters from the 256th one on each time there
          is a line that exceeds that length.  How can I prevent this?

     A10.   Turbo Pascal's readln procedure  reads  a  line up to the 255th
          character then skips to the next line.   To get around  this  you
          should declare a buffer at least as large as the longest possible
          line  and  then  use  the read procedure.   The best size for the
          buffer is a multiple of 2048 bytes.

        const
          BufferSize = 2048;
          LineLength = 78;
        type
          textbuffer = array[1..BufferSize] of char;
        var
          st          : string;
          f           : text;
          buffer      : textbuffer;

        function ReadTxtLn(var tf: text; var s: string; max: byte): integer;
          { Reads a string of a maximum length from a text file }
          var
            len         : byte absolute s;
          begin
            len := 0;
            {$I-}
            while (len < max) and not eoln(tf) do begin
              inc(len);
              read(tf);
            end;
            if eoln(tf) then
              readln(tf);
            ReadTxtLn := IOResult;
            {$I+}
          end; { ReadTxtLn }

        begin
          assign(f,filename);
          reset(f);
          SetTextBuf(f,buffer);
          while not eof(f) and (ReadTxtLn(f,st,LineLength) = 0) do
            writeln(st);
          close(f);
        end.


     Q11.   How do I convert nul  terminated asciiz strings to Turbo Pascal
          strings?

     A11.  Here is a function that will do that -

        function Asc2Str(var s; max: byte): string;
          { Converts an ASCIIZ string to a Turbo Pascal string }
          { with a maximum length of max.                      }
          var starray  : array[1..255] of char absolute s;
              len      : integer;
     PNL #10                      Page 32                         March, 1992



          begin
            len        := pos(#0,starray)-1;              { Get the length }
            if (len > max) or (len < 0) then      { length exceeds maximum }
              len      := max;                         { so set to maximum }
            Asc2Str    := starray;
            Asc2Str[0] := chr(len);                           { Set length }
          end;  { Asc2Str }


     Q12.   How can I tell if a  particular  bit  of a variable is set or not?
          How can I set it?   How can I turn it off?   How can I make a  large
          bit  map  and  then determine if a particular bit - say bit 10000 is
          on/of?

     A12.   This question,  or a variation of it,  is one of the most commonly
          asked questions in the echo and there are several ways of doing what
          is wanted.   None are necessarily  right  or wrong.   The way I will
          describe is designed  to  take  up  as  little  code/data  space  as
          possible.    I  do  not  attempt  to explain the theory behind these
          functions as this can be obtained from any good book.

        The use of sets can be  the  best  bit manipulation method if you have
        control over the data being used.   Here  is  an  example  of  a  byte
        variable for a BBS program which sets various user access level flags.

           Bit 0 = Registered User
               1 = Twit
               2 = Normal
               3 = Extra
               4 = Privileged
               5 = Visiting Sysop
               6 = Assistant Sysop
               7 = Sysop

         type
           status_type  = (Registered,
                           Twit,
                           Normal,
                           Extra,
                           Privileged,
                           VisitingSysop,
                           AssistantSysop,
                           Sysop);
            status_level = set of status_type;

         var
           access_flags  : status_level;

        Let  us  assume you have someone who logs on and you wish to determine
        his user access level.   After reading access_flags from the user data
        file -

             if Sysop in access_flags then ....

        To set the sysop flag -

             access_flags := access_flags + [Sysop];
     PNL #10                      Page 33                         March, 1992



        To reset the sysop flag -

             access_flags := access_flags - [Sysop];

        However on many occasions using  a  set  may not be a suitable method.
        You may simply need to know if bit 5 is set  or  not.    Here  is  the
        method that I consider the best -

          function BitIsSet(var V,  bit: byte): boolean;
            begin
              BitIsSet := odd(V shr bit);
            end;

        To set a bit -

           procedure SetBit(var V: byte; bit: byte);
             begin
               V := V or (1 shl bit);
             end;

        To reset a bit -

           procedure ResetBit(var V: byte; bit: byte);
             begin
               V := V and not(1 shl bit);
             end;

        To toggle (flip) a bit -

           procedure ToggleBit(var V: byte; bit: byte);
             begin
               V := V xor (1 shl bit);
             end;

        Now a bit map can be made up from an array of bytes.   If stored on
        the  heap  you  can  test any bit up to number 524159 (zero based).
        Here's how.

        type
          map = array[0..maxsize] of byte;
          { set maxsize to number of bits div 8 -1 needed in the bit map }

        function BitSetInBitMap(var x; numb : longint): boolean;
          { Tests the numb bit in the bitmap array }
          var m: map absolute x;
          begin
            BitSetInBitMap := odd(m[numb shr 3] shr (numb and 7));
          end;

        procedure SetBitInBitMap(var x; numb: word);
          { Sets the numb bit in the bitmap array }
          var m: map absolute x;
          begin
            m[numb shr 3] := m[numb shr 3] or (1 shl (numb and 7))
          end;

        procedure ResetBitInBitMap(var x; numb : longint);
          { Resets the numb bit in the bitmap array }
     PNL #10                      Page 34                         March, 1992



          var m: map absolute x;
          begin
           m[numb shr 3] := m[numb shr 3] and not(1 shl (numb and 7));
          end;

        procedure ToggleBitInBitMap(var x; numb : longint);
          { Toggles (flips) the numb bit in the bitmap array }
          var m: map absolute x;
          begin
            m[numb shr 3] := m[numb shr 3] xor (1 shl (numb and 7));
          end;


     Q13. How can I find a particular string in any file - text or binary?

     A13.    The  Boyer-Moore  string search algorithm is considered to be the
          fastest method available.   However in a rare worst-case scenario it
          can be  slightly  slower  than  a  linear  brute-force method.   The
          following demonstration program will show how  it  works  and  could
          easily be modified to allow for command line paramters etc.


        program BMSearchDemo;

        type
          bigarray = array[0..32767] of byte;
          baptr    = ^bigarray;
          BMTable  = array[0..255] of byte;

        const
          KeyStr : string = 'Put whatever you want found here';
          fname  : string = 'f:\Filename.txt';

        var
          Btable : BMtable;
          buffer : baptr;
          f      : file;
          result,
          position : word;
          offset : longint;
          finished,
          Strfound  : boolean;

        procedure MakeBMTable(var t : BMtable; var s);
          { Makes a Boyer-Moore search table. s = the search string}
          { t = the table }
          var
            st  : BMtable absolute s;
            slen: byte absolute s;
            x   : byte;
          begin
            FillChar(t,sizeof(t),slen);
            for x := slen downto 1 do
              if (t[st[x]] = slen) then
                t[st[x]] := slen - x
          end;

        function BMSearch(var buff,st; size : word): word;
     PNL #10                      Page 35                         March, 1992



          { Not quite a standard Boyer-Moore algorithm search routine }
          { To use:  pass buff as a dereferenced pointer to the buffer}
          {          st is the string being searched for              }
          {          size is the size of the buffer                   }
          { If st is not found, returns $ffff                         }
          var
            buffer : bigarray absolute buff;
            s      : array[0..255] of byte absolute st;
            len    : byte absolute st;
            s1     : string absolute st;
            s2     : string;
            count,
            x      : word;
            found  : boolean;
          begin
            s2[0] := chr(len);
            { sets the length to that of the search string }
            found := false;
            count := pred(len);
            while (not found) and (count < (size - len)) do begin
              if (buffer[count] = s[len]) then
              { there is a partial match } begin
                if buffer[count-pred(len)] = s[1] then
                { less partial! } begin
                  move(buffer[count-pred(len)],s2[1],len);
                  found := s1 = s2;
                  { if = it is a complete match }
                  BMSearch := count - pred(len);
                  { will stick unless not found }
                end;
                inc(count);    { bump by one char - match is irrelevant }
              end
              else
                inc(count,Btable[buffer[count]]);
                { no match so increment maximum }
            end;
            if not found then
              BMSearch := $ffff;
          end;  { BMSearch }


        begin
          new(buffer);
          assign(f,fname);
          reset(f,1);
          offset := 0;
          MakeBMTable(Btable,KeyStr);
          repeat
            BlockRead(f,buffer^,sizeof(buffer^),result);
            position := BMSearch(buffer^,KeyStr,result);
            finished := (result < sizeof(buffer^)) or (position <> $ffff);
            if position = $ffff then
              inc(offset,result);
            Strfound := position <> $ffff;
          until finished;
          close(f);
          if Strfound then
            writeln('Found at offset ',offset)
     PNL #10                      Page 36                         March, 1992



          else
            writeln('Not found');
        end.

     Q14. How can I put a apostrophe in a string?

     A14. Just put in extra apostrophes.   If you want st to be equal to
          the  string - The word 'quoted' is in quotes do this -

            st := 'The word ''quoted'' is in quotes';

          if you want the following to be written to screen -
             'This is a quoted string'
          do this -

            writeln('''This is a quoted string''');


     Q15. What are the best books to purchase to help me learn Turbo Pascal?

     A15. There are many good books for learners.  Here are a few -

          Complete Turbo Pascal - Third Edition - Jeff Duntemann
          Mastering Turbo Pascal 6 - Tom Swann
          Turbo Pascal - The Complete Reference - O'Brien.

          For  advanced  users  there are also many good books.   Here are a
     few
          that I have found  useful  -  (Those  marked  with an asterisk are
     not
          purely for Turbo Pascal)

          Turbo Pascal 6 - Techniques and Utilities - Rubenking
          Turbo Pascal Internals - Tischer
          * PC System Programming for Developers - Tischer
          * Undocumented DOS - Schulman

          Any learner would be well advised to obtain a well known library
     such
          as  Technojock's  Turbo Toolkit (TTT) which is shareware and study
     the
          source code.

     Trevor Carlsen
     (TeeCee)
     PNL #10                      Page 37                         March, 1992



     =================================================================
        Running 80286 Turbo Pascal Programmes on 8088/8086 Computers
     =================================================================

     [ Editor's note: Intentionally,  the author uses English spelling instead
     of American spelling ]

     Introduction

     When Borland released Turbo Pascal Version 6.0 in 1990,  they added  many
     impressive  features to the compiler which reaffirmed Turbo Pascal as the
     de facto standard Pascal  compiler  for  PCs.    One  such feature is the
     ability to generate iAPX286 instructions for added speed on AT or  higher
     machines;  unfortunately,  they forgot (neglected?) to add a check to the
     RTL (runtime library) for whether or not an iAPX286 was actually present.
     Any  programme  compiled  in the {$G+} state will undoubtedly "hang" when
     executed on an 8088 or 8086-based computer.

     In what  seems  an  afterthought,   Borland  added  a small demonstration
     programme called "TEST286" in  the  \DEMOS  subdirectory  of  your  Turbo
     Pascal  subtree,   and  wrote  a small mention in the "HELPME!.DOC" file.
     Following Borland's own advice, however:

          "If you want to put code like this in  a  program  with
          {$G+}  enabled,   put  the  test  and  halt code in the
          initialization section of  the  first  unit in the main
          program's USES clause."

     What follows is a  description  of  a  unit (provided) which does exactly
     that.  Place the unit's label immediately after the "uses" keyword before
     any other unit labels,   and  forget  about  it;   at  runtime  when  the
     initialisation code is executed and no 80286, 80386 or 80486 is detected,
     a message is displayed informing the user of the reason why the programme
     immediately thereafter exits (returning an errorlevel of one).


     Directives Overview

     This  section  merely  explains  the  logic  behind  each of the compiler
     directives used for the benefit of  novices,   and can be ignored by more
     competant programmers.

     As presented,  the unit is compiled in the {$G-} state to  ensure  proper
     execution on computers based on the 8088 or 8086 processor.   Further, it
     is  compiled with far calls and overlay ability enabled (viz.,  {$F+} and
     {$O+}) so upon completion in large applications,  the code can be swapped
     out (although this shouldn't be too  necessary as the entire unit is less
     than one kilobyte).

     The routine exhibited no problems on the test  machines  (an  80486,   an
     80386  AST  and  an  NEC  V20-based XT),  so has all debugging facilities
     disabled ({$D-,I-,L-,R-,S-}).   This should  not  prove  a problem in any
     programmes in which you have debugging enabled,  unless you wish to trace
     through the code.

     In addition to not including debugging  information,   the  "usual"  code
     optimisation  switches  are enabled ({$B-,V-}).   Since word-aligned data
     has no effect on the  8088,   yet  increases execution speed on all 80x86
     PNL #10                      Page 38                         March, 1992



     processors,  it is enabled ({$A+}).   There is no use  of  the  "extended
     syntax" option (again, a Version 6.0 enhancement), so it is disabled with
     {$X-}.

     The  addition  of  {$E-}  and  {$N-}  is  merely to "complete" the switch
     directives, and indicate that there is no need for any additional numeric
     or emulated floating point processing.


     Unit Description

     As there are no functions  or  procedures  to call (variables to read and
     alter,  constants to use,  et al.),  the "interface" section of the  unit
     remains void (empty).

     Both  the  errorlevel returned,  and the message displayed are defined as
     constants so that they  can  be  easily  altered  by an external setup or
     configuration utility.   The errorlevel can be any value from zero (0) to
     two hundred and fifty-five (255),  and the  error  message  can  be  upto
     eighty  (80)  characters  in length.   For users of Object Professional's
     OpClone unit, the string constant "i286 config data 1.00" is provided.

     The one internal variable  declared,   "Is286Able",   is used to pass the
     result of the detection routine to the remainder of the code;   this  was
     necessary as no separate function is used (to keep execution speed to the
     bare  minimum),  and standard functions and procedures (such as WriteLn()
     and Halt()) are not  callable  from  within the built-in inline assembler
     (one short-coming of BASM).

     The core of the original function provided in TEST286 is:

          asm
            pushf               { Push flags register onto the stack     }
            pop    bx           { Pop a word from the stack, store in BX }
            and    bx, 00FFFh   { Logical AND operands, result in BX     }
            push   bx           { Push word contents of BX on the stack  }
            popf                { Pop from stack into flags register     }
            pushf               { Push flags register onto the stack     }
            pop    bx           { Pop a word from the stack, store in BX }
            and    bx, 0F000h   { Logical AND operands, result in BX     }
            cmp    bx, 0F000h   { Compare operands, update status flags  }
            mov    ax, 00h      { Store word 0x0000 (zero) in AX         }
            jz     @@1          { If the zero flag is set, jump to "@@1" }
            mov    ax, 01h      { Store word 0x0001 (one) in AX          }
          @@1:                  { Internal label "@@1", end of routine   }
          end;

     The essential logic here is that for processors earlier  than  the  80286
     (viz.,  8088,  8086,  80188, 80186, V20 and V30) bits 12 to 15 of the CPU
     flags  register  cannot  be cleared.   This routine merely tries to clear
     those bits (without disturbing the others), and if it is unable to do so,
     assumes an iAPX286 or higher (i386, i486) is not present.

     For the supplied unit, the core is:

          asm
            xor    ah, ah       { Logical XOR operands, result in AH     }
            pushf               { Push flags register onto the stack     }
     PNL #10                      Page 39                         March, 1992



            pop    bx           { Pop a word from the stack, store in BX }
            and    bx, 00FFFh   { Logical AND operands, result in BX     }
            push   bx           { Push word contents of BX on the stack  }
            popf                { Pop from stack into flags register     }
            pushf               { Push flags register onto the stack     }
            pop    bx           { Pop a word from the stack, store in BX }
            and    bx, 0F000h   { Logical AND operands, result in BX     }
            cmp    bx, 0F000h   { Compare operands, update status flags  }
            je     @@1          { If the zero flag is set, jump to "@@1" }
            inc    ah           { Increment the contents of AH by one    }
          @@1:                  { Internal label "@@1", store result of  }
            mov    [Is286Able], ah   { detection in "Is286Able" variable }
          end;

     Logically,  the two code segments  are  identical (ignoring the last "mov
     [Is286Able], ah" instruction above).  If we compare their logic structure
     in English:

          (Original Code)
          1. Clear flag bits 12-15.
          2. Compare - did they clear?
          3. Set the result to zero (false).
          4. If the comparison in #3 resulted in the zero flag
             being set, ie., the bits did NOT clear, jump to
             the end of the routine.
          5. The bits DID clear, so set the result to one (true).

          (New Code)
          1. Set the result to zero (false).
          2. Clear flag bits 12-15.
          3. Compare - did they clear?
          4. If the comparison in #3 resulted in the zero flag
             being set, ie., the bits did NOT clear, jump to
             the end of the routine.
          5. The bits DID clear, so increment the result to one (true).

     Up to this point,  the comparison has been discussed as resulting in  the
     zero flag being set or cleared; to understand why, you must remember that
     comparisons are conducted by subtraction, so that if two items equal each
     other  numerically,  their difference is zero (hence the the zero flag is
     set).   To indicate more accurately  the  logic of the routine,  the "jz"
     (jump if zero set) instruction was replaced with the "je" (jump if  equal
     - ie., zero set) instruction.  Both are functionally identical.

     Saving Clock Cycles

     The differences remain now with two instructions:  "xor ah,  ah" and "inc
     ah".   Since only eight bits are needed for the result, AH is used rather
     than AX in its entirety.  The zeroing of the register with XOR instead of
     MOV ("xor ah,  ah" instead of "mov ah,  0") saves one clock cycle on 8086
     processors  (none  on  80286,  80386 or 80486 processors) and works since
     performing a logical exclusive OR (as  opposed to a logical inclusive OR)
     on a number with itself always results in zero.

     Moving the placement of  the  "set-result-to-false"  instruction  to  the
     start not only makes more sense,  but is necessary as the XOR instruction
     modifies  the zero flag (which then could not be used in the ensuing jump
     instruction).
     PNL #10                      Page 40                         March, 1992




     Rather than loading AH with  one  ("mov  ah,  1" indicating "true"),  the
     increment instruction is used ("inc ah") as,  with XOR, it saves an extra
     valuable clock cycle on 8086 processors.   Admittedly, if the host CPU is
     an 8086 the increment instruction is never reached,  but it does not hurt
     to optimise at the instruction level.


     Remaining Code

     The last five lines of the routine proper are  in  Pascal  (as  explained
     above,   BASM  doesn't  allow calling standard procedures and functions).
     These merely check the status  of  the  "Is286Able" variable,  and if the
     assembly code set it to "false", print a message before exiting.


     Test Programme

     A driver programme to test the unit is simply:

          program Testi286;
          {$G+}

          uses
            i286;

           {-If a '286 is absent, the init code of the unit will exit}
          begin
            WriteLn('Obviously an 80286 or higher is in this machine.')
          end.   { Testi286 }


     Conclusion

     While many will claim the saving of two (well,  one) clock cycles  is  no
     more  than academic,  this was not the main aim of the unit.   The "i286"
     unit provides a very easy to use, "plug 'n' play" method of detecting and
     exiting which requires no further effort on the part of the programmer.

     David J. N. Begley
     58:2100/142@intlnet, 3:712/211.3@fidonet
     Department of Computing, Faculty of Science and Technology
     University of Western Sydney, Nepean
     PNL #10                      Page 41                         March, 1992



     =================================================================
                              Conclusion
     =================================================================

     That's it for now.

     In the next issue,  I will publish the first part of  what  I  call  the
     Beginner's  Toolbox.   It is a set of units to help beginners to program
     easily.  It will include input/output routines, a help system, a menuing
     system, etc...

     I thank everybody  who  participated  in  the  rebirth of PNL.   Richard
     Morris,  the editor over-the-pond has collected  most  of  the  articles
     here.   Without him, there would have been only 2 articles here by March
     1st, 1992.

     Again,   I  beg you all to send in articles.   Everybody has his/her own
     techniques to program,  comments to share with others.   You can send in
     book reviews or software reviews,

     The newsletter depends on your contribution...

     Anybody interested in  publishing  an  article  can  request the Article
     Specifications with the magic name ARTSPEC on  FidoNet  1:167/405  -  It
     will also be available soon from Richard Morris, FidoNet 3:640/372.5.

     If you would like to receive back issues of PNL directly from me, send a
     diskette and $2.00 for shipping.  Don't forget to include your address.
     
                    Send your order to:
                       Alex Boisvert
                       86 Bryant St.
                       Sherbrooke, Quebec
                       Canada   J1J 3E4
                       
     If  you are a SysOp that will regularly carry PNL and would like to have
     your bulletin board listed as such,   here,  send me a message either by
     postal mail or at one of the electronic addresses  given  on  the  title
     page, with your bulletin board's name, phone number, and your name.

                             Distribution List

     The  following  is  the  phone numbers to bulletin boards known to carry
     PNL.   If you would like your  bulletin board's name and number added to
     or deleted from this list,  please send me a message at one of  my  many
     addresses.    I  can  not guarantee whether a listed board will have any
     particular issue, however.

        Thieve's World ......................... Phone: (713) 463-8053
        Hippocampus ............................ Phone: (203) 484-4621
        Turbo City BBS ......................... Phone: (209) 599-7435
        The Final Frontier BBS.................. Phone: (518) 761-0869
