File Handling In Pascal

From Free Pascal wiki
Jump to: navigation, search

English (en) | Français (fr) | 日本語 (ja) | ‪中文(台灣)‬(zh_TW) | ‪中文(中国大陆)‬ (zh_CN)

Something most programmers need to know how to do is work with files. Files can be used to save user settings, error logs, and more. Here I am going to teach you how to work with basic text files.

Contents

Old procedural style

When using files in classic (non-object oriented) Pascal, you can use a TextFile type, which allows you to write string into the file or create your own file type.

...
type
  TIntegerFile = file of Integer; // Allows you to write Integers into the file
  TPCharFile = file of PChar; // Write PChars into the file :\
  TStringFile = file of String; // Write Strings into the file
...

If we only did TStringFile = File, then it would be impossible to write anything into it! Also, you cannot write integers directly into TStringFile, because it is a file of strings. Better use the filetype TextFile for writing values of different types.

IO

IO is the file handling thingy for Pascal. It tells the compiler how to deal with IO error situations: raise an exception or store the result into the IOResult variable. Since it is a compiler directive, you have to do this:

{$I-} // Turn off checking. This way all errors go into the IOResult variable
{$I+} // Turn it back on; errors will lead to an EInOutError exception

By disabling/turning off $I, file operation results go into the IOResult variable. This is a cardinal (number) type. So, if you want to write IOResult, you have to use the IntToStr function. Different numbers mean different errors. So you may want to check the documentation for the different errors: [1].

File procedures

These file handling procedures and functions are located in unit system. See the FPC documentation for more details: Reference for 'System' unit.

  • AssignFile (or the older Assign) - Assign a name to a file
  • Append - Opens an existing file for appending data to end of file and editing it
  • BlockRead - Read data from an untyped file into memory
  • BlockWrite - Write data from memory to an untyped file
  • CloseFile (or the older Close) - Close opened file
  • EOF - Check for end of file
  • Erase - Erase file from disk
  • FilePos - Get position in file
  • FileSize - Get size of file
  • Flush - Write file buffers to disk
  • IOResult - Return result of last file IO operation
  • Read - Read from a text file into variable
  • ReadLn - Read from a text file into variable and goto next line
  • Reset - Opens a file for reading
  • Rewrite - Open file for writing
  • Seek - Change position in file
  • SeekEOF - Set file position to end of file
  • SeekEOLn - Set file position to end of line
  • Truncate - Truncate the file at position
  • Write - Write variable to a text file
  • WriteLn - Write variable to a text file and append newline


Example

A full example of handling a text file of type TextFile:

program FileTest;
 
{$mode objfpc} // Do not forget this ever
 
uses
 Sysutils;
 
var
 FileVar: TextFile;
 
begin
  WriteLn('File Test');
  // Use AassignFile rather than Assign as Assign is used in other units as well
  AssignFile(FileVar, 'Test.txt'); // You do not have to put .txt but this is just for now
  {$I+} //use exceptions
  try  
    Rewrite(FileVar);  // creating the file  
    Writeln(FileVar,'Hello');
    // Use CloseFile rather than Close as Close is used in other units as well
    CloseFile(FileVar);
  except
    on E: EInOutError do
    begin
      Writeln('File handling error occurred. Details: '+E.ClassName+'/'+E.Message);
    end;    
  end;
  WriteLn('Program finished. Press enter to stop.');  
  ReadLn;
end.

Now open the file in any text editor and you will see Hello written to it! You can test the error handling by running it once, setting test.txt to read-only and try running it again.

Note that we used exception handling ({$I+}) as that is an easy way to perfom multiple file operations and handling the errors. You could also use {$I-}, but then you would have to check IOResult after each operation and modify your next operation.

Here's how appending/adding to a file works:

program EditFile;
 
 
{$mode objfpc}
 
uses
 Sysutils;
 
var
 File1: TextFile;
 
begin
  WriteLn('Append file');
  {$I+}
  try
    AssignFile(File1, 'File.txt');
    Append(File1, 'adding some text...');
    CloseFile(File1);
  except
    on E: EInOutError do
    begin
     Writeln('File handling error occurred. Details: '+E.ClassName+'/'+E.Message);
    end;    
  end;
  WriteLn('Program finished. Press enter to stop.');  
  Readln;
end.

Reading a file:

program ReadFile;
 
 
{$mode objfpc}
 
uses
 Sysutils;
 
var
 File1: TextFile;
 Str: String;
 
begin
  Writeln('File Reading:');
  AssignFile(File1, 'File.txt');
  {$I+}
  try
    Reset(File1);
    repeat
      Readln(File1, Str); // Reads the whole line from the file
      Writeln(Str); // Writes the line read
    until(EOF(File1)); // EOF(End Of File) The the program will keep reading new lines until there is none.
    CloseFile(File1);
  except
    on E: EInOutError do
    begin
     Writeln('File handling error occurred. Details: '+E.ClassName+'/'+E.Message);
    end;    
  end;
  WriteLn('Program finished. Press enter to stop.');  
  Readln;
end.

It is possible to do some file handling using chars instead of strings. This makes it look cool :D.

Object style

In addition to the old style file handling routines mentioned above, a new system exists that uses the concept of streams (streams of data) at a higher abstraction level, which means that you as a programmer have to perform fewer steps when dealing with files.

In addition, most string handling classes have the ability to load and save content from/to file. These methods are usually named SaveToFile and LoadFromFile. A lot of other objects (such as Lazarus grids) have similar functionality, including Lazarus datasets (DBExport); it pays to look through the documentation/source code before trying to roll your own import/export routines.

Binary files

For opening files for direct access TFileStream should be used. This class is an encapsulation of the system procedures FileOpen, FileCreate, FileRead, FileWrite, FileSeek and FileClose which resides in unit FileUtil.

IO routines

In the example below, note how we encapsulate the file handling action with a try..finally block. This makes sure the Filestream object is always released (the finally... part) even if there are file access (or other) errors.

var
  Buffer: array[0..9999] of Byte;
begin
  with TFileStream.Create('SomeFile.bin', fmCreate) do 
  try
    Seek('Hello');
    Write(Buffer, SizeOf(Buffer));
  finally
    Free;
  end;
end;


You can load entire files into memory too if its size is comparatively smaller than available system memory. Bigger sizes might work but your operating system will start using the page/swap file, making the exercise useless from a performance standpoint.

begin
  with TMemoryStream.Create do 
  try
    LoadFromFile('SomeFile.bin');
    Seek(0, soEnd);
    Write(Ord('A'), 1);
    SaveToFile('SomeFile.bin');
  finally
    Free;
  end;
end;

With larger files of many Gb, you may want to read in buffers of, say, 4096 bytes (you're advised to use a multiple of the filesytem cluster or block size) and do something with the data of each buffer read.

var
  TotalBytesRead, BytesRead : Int64;
  Buffer : array [0..4095] of byte;  // or, array [0..4095] of char
  FileStream : TFileStream;
 
try
  FileStream := TFileStream.Create;
  FileStream.Position := 0;  // Ensure you are at the start of the file
  while TotalBytesRead <= FileStream.Size do  // While the amount of data read is less than or equal to the size of the stream do
  begin
    BytesRead := FileStream.Read(Buffer,sizeof(Buffer));  // Read in 4096 of data
    inc(TotalBytesRead, BytesRead);                       // Increase TotalByteRead by the size of the buffer, i.e. 4096 bytes
    // Do something with Buffer data
  end;

FileCopy

With the above, we can implement a simple FileCopy function (FreePascal has none in its RTL although Lazarus has copyfile) - adjust as needed for bigger files etc:

function FileCopy(Source, Target: string): boolean;
// Copies source to target; overwrites target.
// Caches entire file content in memory.
// Returns true if succeeded; false if failed
var
  MemBuffer: TMemoryStream;
begin
  result:=false;
  MemBuffer:=TMemoryStream.Create;
  try
    try
      MemBuffer.LoadFromFile(Source);
      MemBuffer.Position:=0;
      MemBuffer.SaveToFile(Target); //may be same as source
      result:=true;
    except
      result:=false; //swallow exception; convert to error code
    end;
  finally
    MemBuffer.Free;
  end;
end;

Text files

In general, for text files you can use the TStringList class to load the entire file into memory and have simple access to their lines. Of course, you can also save your StringList back to file:

begin
  with TStringList.Create do 
  try
    Add('Hello');
    SaveToFile('SomeFile.txt');
  finally
    Free;
  end;
end;

In order to write a single string to a stream you might want to use the following procedure:

procedure SaveStringToPath(theString, filePath: String);
var
  textFile: TFileStream = nil;
  textLength: integer;
  stringBuffer: ^String;
begin
  textLength := length(theString);
  try
    textFile := TFileStream.Create(filePath, fmOpenWrite or fmCreate);
    { write string to stream while avoiding to write the initial length }
    stringBuffer := @theString + 1;
    textFile.WriteBuffer(stringBuffer^, textLength);
  finally
    if textFile <> nil then textFile.Free;
  end;
end;

See also

Personal tools