How About Binary One





Hello, we meet again ! Nice to see ya ! I know you're eager to finish this last chapter of the first lesson. It's still about the file. It is similar to the previous chapter. If you don't understand chapter 13, you'd better not carry on, but re-learn it instead. This time, we will discuss :

  1. Typed files
  2. Untyped files
  3. File commands
  4. Directory commands

There are two kinds of binary files :

  1. Typed files
  2. Untyped files

Typed file means that the file has a uniform format throughout its contents. This kind of file includes databases, because all of them contains the data records. Simply said, file of records. Untyped file means that the file contains no uniform data. Although you may have seen records stored in this kind of file, that file contains some additional information that may be different record structure. Simply said, file with no distinct records.

First, we discuss typed files. Suppose you define a record like this :

type
  Temployee = record
                name    : string[20];
                address : string[40];
                phone   : string[15];
                age     : byte;
                salary  : longint;
              end;

Typed file of THAT record is defined like this :

var
   F : file of Temployee;

The steps of using typed file is just the same as using text file.

  1. You associate it with file name using assign.
  2. Open it, using reset, OR Create it, using rewrite.
  3. Use it.
    Writeln in text file MUST BE CHANGED into Write and Readln with Read respectively.
  4. Close it using close.

All error handling and IOResult use is all the same, so that I don't have to re-mention it all over again.

The difference is : If you open typed file with reset it doesn't mean that you can only read it (just in the text files), but you may write on it and modify it. The command rewrite is still the same, create a new one, discarding the file previous contents. Then, look at this example :


{ A crude database recording }
uses crt;
type
   Temployee = record
                  name    : string[20];
                  address : string[40];
                  phone   : string[15];
                  age     : byte;
                  salary  : longint;
               end;

var
   F : file of Temployee;
   c : char;
   r : Temployee;
   s : string;

begin
   clrscr;
   write('Input file name to record databases : '); readln(s);

   assign(F,s);           { Associate it }
   rewrite(F);            { Create it    }

   repeat
      clrscr;
      write('Name    = '); readln(r.name);     { Input data }
      write('Address = '); readln(r.address);
      write('Phone   = '); readln(r.phone);
      write('Age     = '); readln(r.age);
      write('Salary  = '); readln(r.salary);

      write(F,r);                 { Write data to file }

      write('Input data again (Y/N) ?');
      repeat
         c:=upcase(readkey);      { Ask user : Input again or not }
      until c in ['Y','N'];
      writeln(c);
   until c='N';

   close(F);
end.

Easy, right ? After creating database, display it. Modify the above program to read the file contents. This is the hint :

  1. Change rewrite to reset.
  2. After the second clrscr (inside repeat..until block), add : read(F,r);
  3. Remove the line "write(F,r)"

That's all. You may alter the displayed message to the appropriate one. Run it and see how it's done. Good ! You've done it !

Now, it's time to understand file pointer. In order to know the current position of the file, Pascal use a file pointer. It's simply points to the next record or byte to read. To move the file pointer, use seek :

      seek(F,recordno);

The recordno simply said the record number. If you want to read the tenth record of the file at any instant, use this :

      seek(F,9);        { Data record number started from 0 }
      read(F,r);        { r is the record variable }

You may conclude that it is easy to access the records. Say the record number, seek it, and read it. Any record number in range could be accessed. In range means not exceeding the maximum number of record inside that file. Therefore, it is called Random File Access.

In the other hand, text files could not behave like that. So that it requires to be handled sequentially. Therefore, there comes the jargon Sequential File Access.

Append DOES NOT work in typed files or untyped files. It is specially designed for text files. Then how can we append data to typed files ? Easy. Follow these steps :

  1. Open the file with reset.
  2. Move the file pointer after the last record using seek.

Reset causes file opened but the file pointer points to the first record. How can we know the number of records that is stored inside a file ? Number of records can be calculated as follows :

       totalrecord := filesize(f);

Here is an example of a 'crude' database. It creates a new database if it is not exist, otherwise it appends data.


{ A crude database recording }
uses crt;
type
   Temployee = record
                  name    : string[20];
                  address : string[40];
                  phone   : string[15];
                  age     : byte;
                  salary  : longint;
               end;

var
   F : file of Temployee;
   c : char;
   r : Temployee;
   s : string;
   n : integer;
begin
   clrscr;
   write('Input file name to record databases : '); readln(s);

   assign(F,s);           { Associate it }
   {$I-}
      reset(F);           { First, open it }
   {$I+}

   n:=IOResult;
   if n<>0 then           { If it's doesn't exist then }
   begin
      {$I-}
         rewrite(F);      { Create it    }
      {$I+}
      n:=IOResult;
      if n<>0 then
      begin
         writeln('Error creating file !'); halt;
      end;
   end
   else                    { If it exists then }
      seek(F,filesize(F)); { Move file pointer to the last record }

   repeat
     :
     :
     :
     { All remains the same }
     :
     :

Now, how can we delete a data ? The only routine that Pascal provides is Truncate. It deletes all data starting from where file pointer points to the end of file. You may wonder how to delete a single data record. This is how : Suppose the record number you want to delete is stored in n.

      for i:=n to totalrecord-1 do
      begin
         seek(f,i);
         read(f,r);
         seek(f,i-1);
         write(f,r);
      end;
      seek(f,totalrecord-1);
      truncate(f);
      dec(totalrecord);

Yes, you move the next record to the deleted record. The second next to the next and so on until the end of data. After that, you can safely truncate the last record, since the last record is already stored in record number totalrecord-1 and the last record would be a mere duplicate. Last step you must make is that you must adjust the totalrecord to comply with present situation (after deletion).

Easy, right ? Oh, yes ! I forgot to mention : Flush cannot be applied to binary files. It's just for text files.

It is unpractical to always having file pointer tracked. You can obtain the file pointer position by using filepos :

     n:=filepos(F);

N will hold the current file position (the record number).

That's all about typed files. You may want to see this program for better details. Run it and learn how it works.


{ A crude database recording }
uses crt;
type
   Temployee = record
                  name    : string[20];
                  address : string[40];
                  phone   : string[15];
                  age     : byte;
                  salary  : longint;
               end;

var
   F : file of Temployee;
   c : char;
   r : Temployee;
   s : string;
   n : integer;
begin
   clrscr;
   write('Input file name to record databases : '); readln(s);

   assign(F,s);           { Associate it }
   {$I-}
      reset(F);           { First, open it }
   {$I+}

   n:=IOResult;
   if n<>0 then           { If it's doesn't exist then }
   begin
      {$I-}
         rewrite(F);      { Create it    }
      {$I+}
      n:=IOResult;
      if n<>0 then
      begin
         writeln('Error creating file !'); halt;
      end;
   end
   else
   begin                  { If it exists then }
      n:=filesize(F);     { Calculate total record }
      seek(F,n);          { Move file pointer PAST the last record }
   end;
   repeat
      clrscr;
      writeln('File position : ',filepos(f));
      write('Name    = '); readln(r.name);     { Input data }
      write('Address = '); readln(r.address);
      write('Phone   = '); readln(r.phone);
      write('Age     = '); readln(r.age);
      write('Salary  = '); readln(r.salary);
      write(F,r);                 { Write data to file }
      write('Input data again (Y/N) ?');
      repeat
         c:=upcase(readkey);      { Ask user : Input again or not }
      until c in ['Y','N'];
      writeln(c);
   until c='N';
   close(F);
end.

Before you fully understand typed files, DO NOT CONTINUE to untyped one, it will just make you more confused.

Text files are usually used for INI files or setting files. Or, if your game needs special setup, you can use this skill to modify AUTOEXEC.BAT, CONFIG.SYS or even *.INI in WINDOWS directory. Typed files are usually done for recording high scores of your game, while untyped ones are for reading your game data : pictures, sounds, etc. Serious applications make an extensive use of file. Databases usually use typed files. Text files are used for making memos. Untyped ones is for reading pictures and sounds, for perhaps, you want to make presentations or just displaying the company logo.

Untyped Files

Now, we're going to discuss the untyped files. The basics is all the same. Imagine you have a typed file, but the record is one byte long. The declaration is a bit different from typed files :

var
  F : file;

This declare F as untyped files. Assign, Reset, Rewrite, and Close are still the same. But write and read is not apply in this case. Use blockwrite and blockread instead. Here is the syntax :

     blockread (f,buffer,count,actual);
     blockwrite(f,buffer,count,actual);

f      is the file variable.
buffer is your own buffer, not Pascal's.
count  is the number of bytes you want to read/write.
actual is the number of bytes that has been read/written.

In untyped files, you must prepare a buffer. A buffer can be records, arrays, or even pointers. Usually, programmers use array instead of records. But, usually, if the file has certain structure, like graphic formats, programmers use records. Let's first concern about array as the buffer. Suppose I declared buffer as array of bytes :

var
   buffer        : array[1..2048] of byte;
   count, actual : word;
   f             : file;

Reading is done by this :

   count:=sizeof(buffer);
   blockread(f,buffer,count,actual);

Variable actual holds the number of bytes that is actually read from the disk. Likewise, writing to disk is done through blockwrite :

   count:=sizeof(buffer);
   blockwrite(f,buffer,count,actual);

You can even specify the number of bytes you want to read. Suppose you want to read just 512 bytes from a file :

   blockread(f,buffer,512,actual);

Writing 512 bytes is just similar to reading. Now, how if the buffer is a record ? Suppose I declare the record :

type
   THeader = record
                tag          : string[4];
                width, depth : word;
                bitperpixel  : byte;
             end;

var
   hdr : THeader;

That kind of header is one example of reading picture file header. Usually, after reset, programmer has to read the header to check validity. Reading the header can be done by blockread :

    blockread(f,hdr,sizeof(hdr),actual);

The operator sizeof returns the number of bytes occupied by the operand or parameter automatically (so that you don't have to count it manually). If the file is good, the header is fully read. That can be checked by this :

    if actual=sizeof(header) then    { The file has a good header }
       :
       :

But .... wait ! I saw somebody using untyped file with write and read. Well, that kind of person treating typed file as untyped one. That causes a LOT of pain. That's why I'm not going to teach it. But, if you insist, you can write me.

That's all about untyped files.

File Commands

Now, we're going to discuss other file commands :

  1. Rename
  2. Erase
  3. Getenv
  4. FSearch, FExpand and FSplit
  5. FindFirst and FindNext
  6. UnpackTime and PackTime
  7. GetFTime and SetFTime
  8. GetFAttr and SetFAttr
  9. DiskFree and DiskSize

Number 3 through 9 need DOS unit

Rename, just like its name is to rename files. You must assign the old file name to a file variable, (the file is not necessarily be opened) then use rename :

     assign(f,oldname);
     rename(f,newname);

Erase, is to erase files. You assign the file you want to erase, then erase it. You may NOT open the file. If you've already opened it, close it first before erasing !

     assign(f,filename);
     erase(f);

You have used the crt unit so long and nothing else. Now, it's time to corporate DOS unit, so you'll probably do this :

uses crt, dos;

You need no files to add as crt and dos are both in SYSTEM.TPL. SYSTEM.TPL is always loaded when Pascal starts. Why do we need DOS unit ? Well many of file routines (that has been mentioned as number 3 thru 9) is in DOS unit. Also, interrupt handling, system time and other handy things reside in it. Let's cover the file-handling routines.

Getenv

Getenv fetches the environment string of DOS. Go to command prompt of DOS, then type SET then press Enter. DOS displays all the environment string, such as PATH, PROMPT, etc. This is example of how to get the PATH contents (s is a string) :

   s:=Getenv('PATH');

Getting PROMPT is similar : s:=Getenv('PROMPT');

FSearch

FSearch do searching files in a specified directory. Suppose you want to search for FORMAT.COM in DOS and WINDOWS directory :


uses dos;
var
  s : string;

begin
  s:=FSearch('FORMAT.COM','C:\DOS;C:\WINDOWS');
  if s='' then
     writeln('FORMAT.COM not found')
  else
     writeln('FORMAT.COM found in ',s);
end.

When found, s returns complete path and filename, otherwise empty. You may extend the directory list (the second parameter of FSearch) using semicolon such as :

  ... FSearch( ... , 'C:\DOS;C:\WINDOWS;C:\SCAN;C:\TOOL');

You may wonder that you can even search a file in the PATH environment variable. Yes, you COULD ! Do it like this :

  ... FSearch( ... , getenv('PATH'));

FExpand

FExpand expands a simple file name into a full name (drive, full directory, and the file name itself). It is especially useful when user inputs a relative directory, like this (s is a string) :

   s:=FExpand('..\README');

S will be like this (for example) : 'C:\PASCAL\LESSON.1\README'

FSplit

It is just contrary to FExpand, splits a fully qualified name into directory, file name, and extension. Example :

    var
      s : string;
      d : dirstr;
      n : namestr;
      e : extstr;

      :
      :
      :
    s:='C:\WINDOWS\WIN.INI';
    fsplit(s,d,n,e);    { d = 'C:\WINDOWS\', n = 'WIN', e = '.INI' }

Look at this program for better details.


uses dos;
var
  s : string;
  d : dirstr;
  n : namestr;
  e : extstr;

begin
  s:=FSearch('FORMAT.COM',getenv('PATH'));
  if s='' then
  begin
     writeln('FORMAT.COM not found');
     halt;
  end;

  writeln('FORMAT.COM found in ',s);
  fsplit(s,d,n,e);
  writeln(d,' ',n,' ',e);
end.

FindFirst and FindNext

FindFirst and FindNext is used just like dir command in DOS. It uses the TSearchRec record tag. The syntax of them :

      findfirst(qualifier,attribute,searchrec);
      findnext(searchrec);

qualifier can be '*.*' or '*.PAS' or any wildcard, just like the parameter in dir command. The attribute can be :

AttributeIf you want to search for
ReadOnly
read-only files
Hidden
hidden files
SysFile
system files
VolumeID
disk volume label
Directory
directories
Archive
archive files
AnyFile
any files

TSearchrec is defined as follows :

     type
        TSearchRec = record
                        Fill: array[1..21] of Byte;
                        Attr: Byte;
                        Time: Longint;
                        Size: Longint;
                        Name: array[0..12] of Char;
                     end;

Suppose I have the variable s declared as TSearchrec, and I'm using the findfirst to search '*.EXE' with any attribute :

      findfirst('*.EXE',AnyFile,s);

      s.name holds the name
      s.size holds the size in bytes
      s.time holds the date and time when the file is created
      s.attr holds the attribute

You should never touch the fill field. It is classified. I personally don't know what does it for, only folks in Microsoft did, perhaps.

FindFirst is used to INITIATE the search. FindNext is used to do the subsequent search. You may repeat FindNext as many as you want to retrieve all the filenames you desire. If there are no file names left, the variable DosError is set to 18. For safety reason, you could repeat findnext while the DosError remains 0, like this :


uses dos;
var
  s : TSearchrec;
  q : string;

begin
  write ('Enter qualifier to search = '); readln(q);

  findfirst(q,AnyFile,s);
  while DosError=0 do
  begin
    writeln(s.name);
    findnext(s);
  end;
end.

You may wonder how file date and time can be depicted as long integer. DOS is actually packed them. So, after the call of findfirst and findnext, the s.time is in a packed form. How to unpack them ? Use unpacktime. The syntax is :

    unpacktime(s.time,dt);

s.time is the packed form, dt is the unpacked form. Dt is a record of DateTime, defined as follows :

    type
       DateTime = record
                     Year,Month,Day,Hour,
                     Min,Sec: Word;
                  end;

So, after unpacktime :
dt.year holds the year
dt.month holds the month, and so on (respectively).

Look at the following example, that display the file with its size and its date/time :


uses dos;
var
  dt : DateTime
  s  : TSearchrec;
  q  : string;

begin
  write ('Enter qualifier to search = '); readln(q);

  findfirst(q,AnyFile,s);
  while DosError=0 do
  begin
    unpacktime(s.time,dt);
    write  (s.name :15,    s.size:8,'   ');
    write  (dt.month:2,'/',dt.day:2,'/',dt.year:4,'   ');
    writeln(dt.hour :2,':',dt.min:2,':',dt.sec);
    findnext(s);
  end;
end.

How about the attribute ? Is it packed too ? Not exactly. To detect each attribute you must do this :

    if s.attr and ReadOnly = ReadOnly then write('Read only ');
    if s.attr and Hidden   = Hidden   then write('Hidden ');
    if s.attr and SysFile  = SysFile  then write('System ');
    :
    :
    and so on.

You can do that detection routine to any attribute name shown above EXCEPT for AnyFile.

How can I search for ReadOnly or Hidden files only, but not archives and system files ? Do this at findfirst :

    findfirst(q,ReadOnly or Hidden,s);

You may combine the attribute with OR, not and. Also, you may not combine AnyFile with others, because the combination would not take effect.

Actually, processing attributes is a bit-wise operation, which I haven't taught you yet (later in lesson 2). But I made it as simple as possible, so that you can understand.

PackTime, GetFTime, and SetFTime

UnpackTime has been described above. Now you may wonder how to PackTime. PackTime is used like this :

    packtime(dt,n);

dt is DateTime variable (unpacked time), n is a long integer holds packed time. What does it for ? It's for setting the file time by setftime :

    setftime(f,n);

F is any file variable (text, typed or untyped), n is the packed form of date/time. Don't forget to assign f with a file name first.

GetFTime returns the date/time of the file :

    getftime(f,n);

F and n is the same as in setftime. After getftime, don't forget to unpack it first with unpacktime.

GetFAttr and SetFAttr

Likewise, you can set or get a file's attribute using setfattr and getfattr. Suppose f is any file variable (text, typed, or untyped), and n is a word variable :

     getfattr(f,n);

This will get the file attribute in n. Don't forget to associate f with a file name first. Detecting the attribute is just the same as shown in findfirst and findnext. Setting attribute takes the same syntax. Suppose you want to set the file as hidden and read-only :

     n:=ReadOnly or Hidden;
     setfattr(f,n);

DiskFree and DiskSize

DiskFree returns the number of bytes free in a specified drive. DiskSize returns the disk total size of a drive. Both take a byte as parameter :

    n:=DiskFree(d);    { d is a byte, n is a longint }
    n:=DiskSize(d);

d is the drive qualifier. D = 0 means that you want to search info in the current drive, d = 1 for drive A, d = 2 for drive B, d = 3 for drive C and so on. If n = -1, means that you have input an invalid drive.

Directory commands

We'll discuss these directory commands :


  1. Chdir
  2. Mkdir
  3. Rmdir
  4. Getdir

Chdir, Mkdir, and Rmdir is just the same as DOS command cd, md, and rd. All of them takes a string parameter :

      chdir('\DOS');     { same as cd\dos  }
      mkdir('\TEMP');    { same as md\temp }
      rmdir('\TEMP');    { same as rd\temp }

Getdir is used to get the directory, just like you type cd in DOS prompt, then press enter. The difference is that getdir can be used to get the current directory of any drive, not just current drive as cd does. Getdir syntax is like this :

      getdir(d,s);  { d is a byte, s is a string }

d is the drive qualifier. D = 0 means that you want to search info in the current drive, d = 1 for drive A, d = 2 for drive B, d = 3 for drive C and so on. S will return the directory. If d is invalid s just say the root directory. Suppose d:=23 (drive W) is invalid, s will return 'W:\' as it were a root directory.

Phew ! A tedious lesson has finished. That's all folks ! If you DO understand what I've taught so long, then congratulations ! If you might want to continue the second lesson, wait for another issue ! So long !


Where to go ?

Back to main page
Back to Pascal Tutorial Lesson 1 contents
To the quiz
Back to Chapter 13 about text files
To Lesson 2
My page of programming link
Contact me here


By : Roby Joehanes, © 1997, 2000