Changing File Position with Seek and Position
In the examples above, whenever you wrote or read from the file, the file's position was automatically changed to the location where the write or read ended. You can use the TFileStream.Postion property to get or set where the file is written to or read from. Position gets or sets the Byte location for the file operation to begin with that byte (it is zero based). Position uses the Seek function to change the file position. The function
TFileStream.Seek(Offset: Integer; Origin: Word): Integer;
also resets the current file position, and also has a reference in the Origin parameter for where in the file to apply the Offset parameter. Let's look at a code example that changes the FileStream Position and reads a variable from the new Position. Keep in mind that there are Four Bytes in an Integer variable and that the FileStream Position is zero based:
Simba Code:
procedure ReadFromFile;
var
FileStream1: TFileStream;
Int1, Int2, Int3, Int4: Integer;
Pnt1: TPoint;
str1: string;
begin
FileStream1.Init('D:\Test1.file',
$0000 or $0020);
try
FileStream1.ReadBuffer(Int1, SizeOf(Int1));
FileStream1.ReadBuffer(Int2, SizeOf(Int2));
FileStream1.ReadBuffer(Int3, SizeOf(Int3));
FileStream1.ReadBuffer(Int4, SizeOf(Int4));
FileStream1.SetPosition(4);
FileStream1.ReadBuffer(Pnt1, SizeOf(Pnt1));
finally
FileStream1.Free;
end;
Str1 := IntToStr(Int2)+' '+
IntToStr(Int3)+' '+
IntToStr(Pnt1.x)+' '+
IntToStr(Pnt1.y);
WriteLn(str1);
end;
The 4 integers are read from the file just like we have done before, and then the FileStream1 Position is set to 4 with
FileStream1.Position := 4;
and the Pnt1 (TPoint) variable is read from the file. You will see in the ShowMessage text that the Int2 and Pnt1.x values are the same, and the Int3 and Pnt1.y values are also the same. If you set the FileStream1 Position to 8 and read the Pnt1 variable, the Pnt1.x will equal the Int3 and the Pnt1.y will equal the Int4.
You can also set the FileStream1 Position with the Seek Method. If you use the soFromBeginning in the Origin parameter, then the Seek function is the same as the Position property. So
will set the file position to the same location as
FileStream1.SetPosition(4);
and from the End of the file -
Simba Code:
FileStream1.Seek(-12, 2);
will also set it to the same file position offset in this 16 byte file. Notice that the Offset is a negative number, which means that the file position is moved 12 bytes towards the begining of the file, decreasing the Position by 12 bytes from the end of the file. Which will result in a FileStream1.Position of 4.
Reading Different Data Out of File
I have been using integers and integer Records (TPoint, TRect). So as an example for you about data types in files, not as read methods, in this next code lets use some non-integer variables. There are 4 byte variables, 2 Word variables, which will be read from the same file used before with 4 integers in it. Also, this time I will try to read data past the end of the file to show you what happens:
Simba Code:
procedure ReadFromFile;
var
FileStream1: TFileStream;
Byte1, Byte2, Byte3, Byte4: Byte;
Word1, Word2: Word;
Int1: Integer;
str1: string;
begin
FileStream1.Init('D:\Test1.file',
$0000 or $0020);
try
FileStream1.ReadBuffer(Byte1, SizeOf(Byte1));
FileStream1.ReadBuffer(Byte2, SizeOf(Byte2));
FileStream1.ReadBuffer(Byte3, SizeOf(Byte3));
FileStream1.ReadBuffer(Byte4, SizeOf(Byte4));
FileStream1.ReadBuffer(Word1, SizeOf(Word1));
FileStream1.ReadBuffer(Word2, SizeOf(Word2));
WriteLn(IntToStr(FileStream1.GetPosition));
if FileStream1.Read(Int1, SizeOf(Int1)) < SizeOf(Int1) then
WriteLn('Could not Read file past the end');
finally
FileStream1.Free;;
end;
WriteLn(IntToStr(Byte1)+' '+IntToStr(Byte4)+
' '+IntToStr(Word1)+' '+IntToStr(Word2));
end;
All of these non-integer variables are also read out of the integer file without warning from the compiler, because a TFileStream can NOT type check any variable that is read from a file, and the Buffer parameter is an UnTyped variable, so YOUR CODE is the only thing that determines what the bytes read from a file are used for. The Byte and Word variables will read the bytes out of a Part of the integer values in the file, the Char variables will read one byte of the integer value and translate that into a the Char Letter for that byte value, after reading the file, the four Char variables should be set to 'C', 'h', 'a', 'r', which is why the Int3 value was 1918986307 when we wrote the file. The aryChar1 variable is read from the file using the same method as a Char or integer variable, because it is a Fixed Length Array, and the Charaters read from this integer file in the aryChar1 should be 'Size' because the Int4 was written as 1702521171. If a String or Dynamic Array (variable length) is used, then a different method is used to write and read variables with data that is NOT a fixed memory size, like dynamic Arrays, Strings, PChar, and Bitmaps. I will show these methods later in the Using Non-Fixed Size Variables in Files.
Fixed Size Variable Example
Next is an example for how to Write and Read several fixed size variables in a file, which is for methods you may really use. This uses a variable (FixStr1) with the String[15] Type, this is a Fixed Length String and can be used like an Integer for reading and writing. There is a TMyRec Record defined, this is made up of Fixed Size variables and can be used like the other fixed size variables for file read and write.
Writing the file. . Please Notice that in all of the Write functions, the Count parameter is always set to the SizeOf(Var) for the variable that is being written. You can only use the SizeOf for Fixed Size variables, it will NOT work correctly for Non-Fixed size variables like a long string.
Simba Code:
type
TMyRec = Record
aInt: Integer;
aRect: TRect;
asStr28: String[28];
end;
procedure WriteToFile;
var
FileStream1: TFileStream;
Char1: Char;
Int1: Integer;
Pnt1: TPoint;
Rect1: TRect;
FixStr1: String[15];
MyRec1: TMyRec;
begin
Char1 := 'C';
Int1 := 100;
Pnt1.x := 500;
Pnt1.y := -20;
Rect1.Bottom:=100;
Rect1.Left:=200;
Rect1.Right:=300;
Rect1.Top:=400;
FixStr1 := 'Fixed String';
MyRec1.aInt := 44;
MyRec1.aRect.Bottom:=1;
MyRec1.aRect.Left:=2;
MyRec1.aRect.Right:=3;
MyRec1.aRect.Top:=4;
MyRec1.asStr28 := 'Some Text';
FileStream1.init('D:\Test2.file',
$FF00 or $0001 or $0020);
try
FileStream1.WriteBuffer(Int1, SizeOf(Int1));
FileStream1.WriteBuffer(Pnt1, SizeOf(Pnt1));
FileStream1.WriteBuffer(Rect1, SizeOf(Rect1));
FileStream1.WriteBuffer(FixStr1, SizeOf(FixStr1));
FileStream1.WriteBuffer(MyRec1, SizeOf(MyRec1));
finally
FileStream1.Free;
end;
end;
Reading from this file uses the same method for every variable read, as you can see in the following code. Please Notice that the variables are read in the same exact order that they were written to the file. . Also on the Last Variable Read (MyRec1) there is a Test to see if the Bytes Read are less than the SizeOf(MyRec1), if the Error message is shown, then you know that you have not correctly written, or read the file.
Simba Code:
procedure ReadFromFile1;
var
FileStream1: TFileStream;
Char1: Char;
Int1: Integer;
Pnt1: TPoint;
Rect1: TRect;
FixStr1: String[15];
MyRec1: TMyRec;
begin
FileStream1.Init('D:\Test2.file',
$0000 or $0020);
try
FileStream1.Read(Char1, SizeOf(Char1));
FileStream1.Read(Int1, SizeOf(Int1));
FileStream1.Read(Pnt1, SizeOf(Pnt1));
FileStream1.Read(Rect1, SizeOf(Rect1));
FileStream1.Read(FixStr1, SizeOf(FixStr1));
if FileStream1.Read(MyRec1, SizeOf(MyRec1)) < SizeOf(MyRec1) then
WriteLn('ERROR - - Could not Read data from file');
finally
FileStream1.Free;
end;
WriteLn(IntToStr(Int1)+' '+
IntToStr(Pnt1.x)+' '+
IntToStr(Rect1.Left)+' '+
IntToStr(MyRec1.aInt));
WriteLn(FixStr1)
end;
Saving Arrays of Records to File
You can have Arrays of records, using records containg Fixed Size Variables and write these arrays to File. You can NOT use the same methods for the Static and Dymanic Arrays availible in Delphi Pascal. With the dynamic array, the SizeOf(DynamicArray) will NOT give you the corret size. For the next examples I will use a TDemo Record defined as:
Simba Code:
TOfDemo = (odOpen, odRead, odWrite, odFind, odClose);
TDemo = record
aByte: Byte;
aInt: Integer;
FixStr1: String[16];
aOfDemo: TOfDemo;
aReal: single;
aRect: TRect;
WriteTime: integer;
aSet: Set of TOfDemo;
end;
You must make sure that there are no variables in your record or sub-Records that have a variable memory Data stroage allocation, like a String, Pchar (or other Pointer type), or TObject (like a TBitmap, or TStringList). I have included several different variable types in this record to show some of the Fixed-Size variables you can use.
Code for procedure to write array of TDemo records:
Simba Code:
procedure SaveAryFile;
var
aryDemo: Array[0..5] of TDemo;
FStm: TFileStream;
i: Integer;
begin
ShowMessage(IntToStr(SizeOf(aryDemo)));
aryDemo[0].FixStr1 := 'First String';
aryDemo[0].aOfDemo := odRead;
aryDemo[0].aSet := [odWrite, odClose];
aryDemo[5].FixStr1 := 'Last String';
aryDemo[5].aOfDemo := odClose;
aryDemo[5].aSet := [odRead, odFind];
FStm.Init('D:\Test3.file',
$FF00 or $0001 or $0020);
try
i := High(aryDemo);
FStm.WriteBuffer(i, SizeOf(i));
for i := 0 to High(aryDemo) do
FStm.WriteBuffer(aryDemo[i], SizeOf(TDemo));
finally
FStm.Free;
end;
end;
I have a six member array Array[0..5] of TDemo; , which I will write to a file. I place some data in the first and last TDemo records just for this demo to read out later. Since I am writting this file, I will know that this array has 6 members (6 TDemo records) in it, but I will place the High Index of this array as the first data segment of the file. That way I can read this value (data) out of the file and test to see if it is the correct amount of array members needed to read into the array of TDemo. And then I have a FOR Loop to run through the array and write all of it's TDemo records to file using the TFileStream WriteBuffer method with the SizeOf(TDemo) as the Count parameter.
Code to read this array file:
Simba Code:
procedure LoadRecAry;
var
aryDemo: Array[0..5] of TDemo;
FStm: TFileStream;
i: Integer;
begin
Fstm.Init('D:\Test3.file',
$0000 or $0020);
try
FStm.ReadBuffer(i, SizeOf(i));
if i <> High(aryDemo) then
begin
WriteLn('ERROR - File does NOT have correct array amount');
Exit;
end;
FStm.ReadBuffer(aryDemo[0], SizeOf(aryDemo));
finally
FStm.Free;
end;
if aryDemo[0].aSet = [odWrite, odClose] then
WriteLn(aryDemo[5].FixStr1);
end;
Here I create the TFileStream and read one integer from the file, I test this integer to see if it is the same as the High Index of the array I want to load into. If it is, then I then Read the TFileStream for the array data. But PLEASE NOTICE, I use a different method to read this array, I do NOT have a FOR Loop that will read each TDemo record, , instead I read the entire array from the file at one time. With the line -
FStm.ReadBuffer(aryDemo[0], SizeOf(aryDemo));
I do not need to FOR Loop an array from file, if it only contains Fixed size variables. I could have used the FOR Loop code -
Simba Code:
for i := 0 to High(aryDemo) do
FStm.ReadBuffer(aryDemo[i], SizeOf(TDemo));
And I would have gotten the correct data out. . .
Also, I could have used different code to write the array to the file -
Simba Code:
FStm.WriteBuffer(aryDemo[0], SizeOf(aryDemo));
And would also have written the file susccesfully. I hope you can see that even though the variable aryDemo is defined as an array, it is just one continuous block of memory, the size of 6 TDemo records. So you can write or read this array from file in one operation. But if you have any changing size variables (String, PChar) in your record type, you can NOT write or read the array in one operation. . . . .
With Dymanic Arrays you can NOT use the SizeOf( ) function, instead you will need to calulate the "Size" of the memory block with math, you can multiply the Length of the array by the Size of the Record, like this -
Simba Code:
FStm.WriteBuffer(aryDemo[0], Length(aryDemo) * SizeOf(TDemo);
You should know enough now about TFileStream to experiment and try writting and reading your own files with several different fixed size variables in them. You should try making your own file write and read procedures and change the types of Fixed-Size variables and their order. Create your own Record type with Fixed-Size variables and write and read it in a file. Remember to use the same exact sequence of reading the variables as you used to write the file.
to be continued...