Results 1 to 5 of 5

Thread: A lot about using TFileStream in LaPe. Reading and Writing your custom files.

  1. #1
    Join Date
    May 2012
    Location
    Moscow, Russia
    Posts
    661
    Mentioned
    35 Post(s)
    Quoted
    102 Post(s)

    Default A lot about using TFileStream in LaPe. Reading and Writing your custom files.

    Hey. As I noticem a many peoples doesn't know, how to use TFileStream class in Simba. Well, I have a bit of free time, and I hope - this article may be helpful for your scripting.

    TFileStream

    A file is the basic way to reference drive storage, that enables a computer to distinguish one set of information from another on a drive. TFileStream enables programs to read from and write to a file on a drive. It can be used whenever you need to access the bytes of a File. Using the TFileStream will require you to know some about the things (byte Data) that you will want to write to file, such as the number of bytes in that Data and the arrangement of those bytes, in Records and structures. File access is based on exact "File" byte position reference number, TFileStream will do much of the file positioning automatically for writting and reading.
    Simba Code:
    TFileStream.Init(const FileName: string; Mode: Word);

    Put the name of the file and the way the file should be opened as it's parameters. The FileName parameter has to be a valid file path for the system it's running on, if it is not a valid path then an exception is thrown. The Mode parameter sets how the file is to be opened and consists of an open mode and a share mode. These modes can be set to determine "How" the file is opened, you can open a file to write to it (fmOpenWrite), or to read from it ($0000), or to do both ($0001). You can also have the function create the file with mode $FF00, which will create the file is it does not exist or erase the file if it already exists.

    Once you have a TFileStream created you can write bytes of data to this file with the WriteBuffer( ) method.
    Simba Code:
    procedure TFileStream.WriteBuffer(const Buffer; Count: Integer);

    Or you can Read bytes from this FileStream with it's ReadBuffer( ) method
    Simba Code:
    procedure TFileStream.ReadBuffer(var Buffer; Count: Integer);

    The first parameter of both of these fuctions (Buffer) is an UnTyped variable, which means that you can put almost any variable in this parameter and the Delphi complier will accept it without type checking (no warings). This is both good and bad, This was nessarary because there are many different data types that can be written to a file, so this variable needs to be very flexible and accept any Type of variable, the bad part is that there is no Type Checking by the compiler, so you can make anykind of misteak using the Buffer parameter and it will compile without warning. So you need to know how to use this UnTyped Variable. If you use a variable of Fixed memory size for it's "Data" Value like Integer, Byte, Char, TPoint, TRect and others. You can just use that variable as the Buffer and put a SizeOf( ) in the Count parameter. The compiler will get the "Value" data bytes of this Buffer variable and write or read it for the file. For variables with changable memory size for it's "Data" value, like String, Pchar (or any Pointer variable), TObject, this method will NOT work. The "Value" data for these changeable memory allocation variables is a 4 Byte (numeric) Pointer address, and has nothing to do with the "Data" (like text in String and PChar) for these variables.

    Using the TFileStream Write and Read Methods

    The following code is an example for writing four integers to a file, in a procedure. This will create a file named "D:\Test1.file'" and overwrite that file if it already exists without warning. When naming this file, I have used a File extention of ".file" which is an uncommon file extention (compared to- .txt .doc .bmp .wav .pas) but you can name your files anyway that is useful for you, with any file extention or no file extention. Be careful if you use a commom file extention!

    Simba Code:
    procedure WriteToFile;
      var
      FileStream1: TFileStream;
      Int1, Int2, Int3, Int4: Integer;
    begin
    Int1 := 100;
    Int2 := 200;
    Int3 := 1918986307;
    Int4 := 1702521171;
    FileStream1.init('D:\Test1.file',
                     $FF00 or $0001 or $0020);
    try
      FileStream1.WriteBuffer(Int1, SizeOf(Int1));
      FileStream1.WriteBuffer(Int2, SizeOf(Int2));
      FileStream1.WriteBuffer(Int3, SizeOf(Int3));
      FileStream1.WriteBuffer(Int4, SizeOf(Int4));
      finally
      FileStream1.Free;
      end;
    end;

    The FileStream1 is created with the fmCreate or fmOpenWrite open Mode and the fmShareDenyWrite share Mode. This means that a New File is created with write access, and no other program can write to this file while it is open. All of our integer variables are given values in the begining of the procedure, an after we create the FileStream1 we can write to this file. The first FileStream1 write buffer is for the variable Int1, an Integer, notice that we use the SizeOf( ) function in the Count parameter. (Do you know the number of bytes this SizeOf function will return for an Integer?) This will write 4 bytes to the file with the bits set in these 4 bytes for the integer value of 100. But these 4 bytes are all that is saved to the file, there is NO information saved to the file about what this data was (an Integer in this case). Next the second Integer Int2 is written to the file. But where are the 4 bytes for this integer written, at the beginning of the File, over top of the Int1 bytes? No, after each write the FileStream position is Advanced to where the last write ended, so the Int2 is added on the end of the file, which would be byte number 5, Also, when you use the FileStream write the file size is automatically increased to the size needed for that write. So we can just use the WriteBuffer method and put as many fixed size variables in the file as we want to. Two more Integers Int3 and Int4 are added to the file, the values for these integers (1702521171, 1918986307) will be explained later.

    the TFileStream.Position - Is a zero based integer value that has the current offset of bytes from the begining of the FileStream. Any operation for reading and writing the FileStream will use the file byte begining at the current FileStream Position. The TFileStream access methods, (Write, WriteBuffer, Read, ReadBuffer, LoadFromStream, SaveToStream, CopyFrom) all start their file operation at the Current FileStream Position and automatically advance the FileStream Position to where that method ends it's operation. You can set the FileStream Position to any location of the file, you can also set the position Past (greater than) the current end of the file, if the FileStream is created with the fmOpenWrite Mode, then the file's Size will be increased to the new FileStream Position past the end of the file. If the FileStream is created with the fmOpenRead mode, then the Position can NOT go higher than the Size of the FileStream.

    IMPORTANT - You must FREE the FileStream to release the file from the system's file handleing. To use operating system resources efficiently, a program should Free a TFileStream as soon as you are finished with it, I usually use a try and finally block to make sure the FileStream is Free.

    So now we have made a Muti Data file with 4 integers in it at file path 'D:\Test1.file' . . . Now we need to Read these 4 integers out of the file. Remember, that since we wrote the file, we know what the data Types are, and where the data segments are located (their file Byte position). Here is some code to read the four integers out of the file!

    Simba Code:
    procedure ReadFromFile;
      var
      FileStream1: TFileStream;
      Int1, Int2, Int3, Int4: Integer;
      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));
      finally
      FileStream1.Free;
      end;
    Str1 := 'Int1-'+IntToStr(Int1)+' Int2-'+IntToStr(Int2)+' Int3='
             +IntToStr(Int3)+' Int4'+IntToStr(Int4);
       WriteLn(str1);
    end;

    This time the FileStream1 is Created with the fmOpenRead mode so we can read from the file. Remember, that there is NO information in this file about what the bytes in this file are for, or the order or type of Data in this file, so you will need to know what Data is in the file to get data out of this file. Since we wrote this file we know the data in it and it's arrangement, so we just read the Data out of the file in the exact same sequence that we put the data into it. After the file is opened, we just read the file one Integer at a time like we did when we wrote the file. Remember, the ReadBuffer procedure will automatically advance the FileStream Position to the end of the read operation or the end of the file, whichever comes first. It can NOT advance the Position past the end of the file. So after the second integer (Int2) is read, the FileStream Position is 8, after the third integer is read the file Position is 12. I have added a String, Str1, which is used to put the Integer values into text, so you can display this string in a TLabel.Caption or ShowMessage( ) to confirm that it has gotten the correct integer values.

    the Bytes in a File do NOT have a Type - As I have said, there is no information about the bytes (data) in the file unless you put this information in the file, and know how to get this information out of the file and use it. I did not put any information into the file about the data of this file, I will deal with that later. The next code examples are meant to show you that the bytes of a file can be read out of the file and used in ANY WAY, without any reguard to what the bytes were used as (their data Type) when the bytes were written to file. The file Size of the "D:\Test1.file" will be 16 bytes (4 bytes for each integer). An Integer is a common variable in Delphi, a TPoint has two integers in it's Record type. So 4 Integers would be the amount of Integers in 2 TPoints. So we can read these 16 file bytes into 2 TPoints, as in the following example:

    Simba Code:
    procedure ReadFromFile;
      var
      FileStream1: TFileStream;
      Pnt1, Pnt2: TPoint;
      Str1: String;
    begin
    FileStream1.Init('D:\Test1.file',
                     $0000 or $0020);
    try
      FileStream1.ReadBuffer(Pnt1, SizeOf(Pnt1));
      FileStream1.ReadBuffer(Pnt2, SizeOf(Pnt2));
      finally
      FileStream1.Free;;
      end;
    Str1 := 'Pnt1.x-'+IntToStr(Pnt1.x)+' Pnt1.y-'+IntToStr(Pnt1.y)+
            ' Pnt2.x='+IntToStr(Pnt2.x)+' Pnt2.y'+IntToStr(Pnt2.y);
      WriteLn(str1);
    end;
    This works because the TPoint has 2 integers in it. The integer Values for the Pnt1, Pnt2, x and y values will be the same as the Integer values we got before in the Int1, Int2, Int3, Int4 integers. What about a TRect. . . it has 4 integers in it. . . you could read a TRect from this FileStream1 with:
    Simba Code:
    procedure ReadFromFile;
      var
      FileStream1: TFileStream;
      Rect1: TRect;
      Str1: String;
    begin
    FileStream1.Init('D:\Test1.file',
                     $0000 or $0020);
    try
      FileStream1.ReadBuffer(Rect1, SizeOf(Rect1));
      finally
      FileStream1.Free;
      end;
    Str1 := 'Rect1.Left-'+IntToStr(Rect1.Left)+
            ' Rect1.Top-'+IntToStr(Rect1.Top)+
            ' Rect1.Right='+IntToStr(Rect1.Right)+
            ' Rect1.Bottom'+IntToStr(Rect1.Bottom);
      WriteLn(str1);
    end;

    Again we get the same integer values from the file in the Rect1 variable. I am showing you these last two examples, not for a way to read files, but so you will see that the bytes in your file are not restricted in how they are read or used. You should notice that there is NO TYPE-CHECKING by the compiler for the bytes read into the TPoints or the TRect. This is not posible, the program has no information about what is in this file. The ONLY thing that sets how and what is read form this file is YOU, in your code.

    to be continued...
    Last edited by CynicRus; 04-04-2014 at 05:32 PM.
    Per aspera ad Astra!
    ----------------------------------------
    Slow and steady wins the race.

  2. #2
    Join Date
    May 2012
    Location
    Moscow, Russia
    Posts
    661
    Mentioned
    35 Post(s)
    Quoted
    102 Post(s)

    Default

    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
    Simba Code:
    FileStream1.Seek(4, 0);
    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...
    Per aspera ad Astra!
    ----------------------------------------
    Slow and steady wins the race.

  3. #3
    Join Date
    May 2012
    Location
    Moscow, Russia
    Posts
    661
    Mentioned
    35 Post(s)
    Quoted
    102 Post(s)

    Default

    Using Non-Fixed Size Variables (like String) in Files

    We have seen that you can use a Fixed Size variable with a TFileStream by setting the "Count" parameter to "SizeOf(Var)", but if you use this method with a Non-Fixed Size Variable, like a standard Delphi Long String, it will NOT work. If you create a FileStream1 and have the variable MyString as a String, then put this line in your code -

    FileStream1.Write(MyString, SizeOf(MyString));

    It will NOT write that String to File. First, the SizeOf(MyString) will always return the value of Four (4 bytes), no matter what the length of the String acually is. . . Next, using the code
    FileStream1.Write(MyString
    the MyString in the Buffer parameter will NOT get the MyString's text charaters, because it is not a "Fixed Size" variable, and the Delphi Compiler uses a different method of getting the "Value" data (text charaters for a string) for all Non-Fixed Size variables. The MyString variable will have a SizeOf(MyString) as 4 bytes, because it's just a reference to a Pointer (the SizeOf a Pointer is 4 bytes). And if you use the code

    FileStream1.Write(MyString, SizeOf(MyString));

    it will write the numeric (Integer) value of the Pointer that MyString represents, but it will not write any of the text that is in MyString. You will not need to know the methods of the Delphi Compiler to use Non-Fixed Size variables in a TFileStream, you will need to know that each Non-Fixed Size variable is just a reference to a Pointer, and you will need a different method to read and write Non-Fixed Size variables to a TFileStream, according to the Type of that variable.

    Using Strings in a TFileStream

    Since the SizeOf(MyString) will not return the number of Charaters in the MyString string, you will need to use the Length(MyString) function to get the number of charaters (Size) of the string, so we can use the Length( ) function in the Count parameter. Since the variable "MyString" is a reference to a Pointer, we can not use that to get the text in the MyString, but the variable MyString[1] is Not a reference to a Pointer, it is a Fixed-Size variable for a Char, the First character in the MyString string. But we need All of the text charaters in MyString, not just one of them. If you remember, the Buffer parameter in the TFileStream.Write( ) is UnTyped, so the compiler will not know that MyString[1] is for a Char variable, and will start to read bytes from that location in memory and continue reading bytes for the amount in the Count parameter. So it will read all of the Text characters out of MyString and write them to the FileStream. Since I will not use methods for file access errors, I will now switch to the ReadBuffer( ) and WriteBuffer( ) procedures. Look at the next code example that writes a String to file:

    Simba Code:
    procedure WriteToFile2;
    var
    MyString: String;
    FileStream1: TFileStream;
    begin
    MyString := 'this is a string with several words in it to be in a file';
    FileStream1.init('D:\Test4.file',
                     $FF00 or $0001 or $0020);
    try
      FileStream1.WriteBuffer(MyString[1], Length(MyString));
      finally
      FileStream1.Free;
      end;
    end;

    If you load "D:\Test4.file" file into NotePad you will see the text - this is a string with several words in it to be in a file in NotePad just like a normal text file. This file "D:\Test4.file" is a normal text file, just like what you would get if you saved the NotePad text to file.

    Reading the file into a String - To read this string file into a string will require one additional step, since we will read the file into a string, we need the string that we read it into to be the correct length. You are used to the Compiler automatically setting the length (memory block size) for Delphi Strings. We need to Set the length of the string because the Compiler will not automatically do it for us in this FileStream Read operation. We can set the Length of the String to the Size of the FileStream with the SetLength procedure. The Size Property of a TStream will give you the number of bytes in that Stream, just what we need to set the Length of our string variable. Look at the next code example for reading a string from a file:
    Simba Code:
    procedure ReadFromFile2;
      var
      FileStream1: TFileStream;
      MyString: String;
    begin
    FileStream1.Init('D:\Test4.file',
                     $0000 or $0020);
    try
      WriteLn(IntToStr(FileStream1.GetSize));
      if FileStream1.GetSize = 0 then Exit;
      SetLength(MyString, FileStream1.GetSize);
      FileStream1.ReadBuffer(MyString[1], FileStream1.GetSize);
      finally
      FileStream1.Free;
      end;
    WriteLn(MyString);
    end;

    Notice that I use
    SetLength(MyString, FileStream1.GetSize);
    to have MyString with enough memory space to receive all of the characters read into it from the file. If you do NOT set the Length for MyString in this example, then nothing will be written into the MyString string, because the MyString string will be empty (a pointer to nil), and because the Buffer Parametr is an UnTyped variable, the Read function will not throw an exception or a warning if the variable is empty (as nil) using the MyString[1] variable (also nil). So you need to remember that you must have a way to get the "Size" or "Length" of the String you are about to Read from file, and then Set the Length of your String variable to the Length of the string you are going to read from the file. In this example, the entire file is the string, so we can use the "GetSize" of the FileStream to set the Length of the string variable.

    Writting and Reading More than One String to a FileStream
    In the examples above, we wrote one string to file and read that single string from the file, but we will need a different method to put more than one string in a file and be able to get those strings out of the file. You could use the method that I showed you and write several strings to the FileStream, and then read all of that file into a single string by setting the length of the string to the FileStream Size. But you would not have the separate strings that you put into the file, you would have just One string with all of the strings in it. We could put a "Delimiter" into the FileStream (as in Comma delimited text) and separate the strings from the single file string by searching for each Delimiter in the large string and separating out the original strings, but this is a String operation and Not a FileStream operation. In order to get the separate file strings out of the multi-string file, we will need to record the length of each string into the file, so we can set the length of each string when we read the string out of the file. So each string in the file is a "File Data Segment", or a part of the file with the Data (bytes) for a single variable. We have used File Data Segments before, when we Wrote and Read several Fixed Size variables to a file. I did not need to talk about Data Segments there, because all of the variables were Fixed Size, and we knew the type of variables and used the SizeOf( ) function to know the number of bytes to write and read for that variable. But now we need to use several string variables that are not Fixed Length (Size of bytes), so we will need to write the Length of the string (Data Segment) to file before writting the string's text to file. In the next code example I use a Cardinal variable (Len1) to write the Length of each string to the FileStream, before we write the string.

    Simba Code:
    procedure WriteToFile2;
    var
    FileStream1: TFileStream;
    StrOne, StrTwo, StrThree: String;
    Len1: Cardinal;
    begin
    StrOne := 'First string in this file';
    StrTwo := 'the Second file string';
    StrThree := 'String number three of this file';

    FileStream1.init('D:\strings.msf',
                     $FF00 or $0001 or $0020);
    try
      Len1 := Length(StrOne);
      FileStream1.WriteBuffer(Len1, SizeOf(Len1));
      FileStream1.WriteBuffer(StrOne[1], Len1);

      Len1 := Length(StrTwo);
      FileStream1.WriteBuffer(Len1, SizeOf(Len1));
      FileStream1.WriteBuffer(StrTwo[1], Len1);

      Len1 := Length(StrThree);
      FileStream1.WriteBuffer(Len1, SizeOf(Len1));
      FileStream1.WriteBuffer(StrThree[1], Len1);
      finally
      FileStream1.Free;
      end;
    end;

    We get the Length of the String into Len1 and then write Len1 to the FileStream. The Buffer parameter of TFileStream.Write must be a Variable, so we can NOT use the function Length(StrOne) as the Buffer parameter. Next we write the string to the FileStream, just like we have done before. You must make sure that you write a Length number (Cardinal) before you write the string for Every string that you write.

    In the next code example, I will read the three strings from the FileStream, you will need to read the Len1 variable to get the Data Segment (string) Length and then use the SetLength( ) function for each string before we read the file Data into the string:

    Simba Code:
    procedure ReadFromFile2;
    var
      FileStream1: TFileStream;
      StrOne, StrTwo, StrThree: String;
      Len1: Cardinal;

    begin
    FileStream1.Init('D:\strings.msf',
                     $0000 or $0020);
    try
      FileStream1.ReadBuffer(Len1, SizeOf(Len1));
      if Len1 > 2048 then Exit;
      SetLength(StrOne, Len1);
      FileStream1.ReadBuffer(StrOne[1], Len1);

      FileStream1.ReadBuffer(Len1, SizeOf(Len1));
      if Len1 > 2048 then Exit;
      SetLength(StrTwo, Len1);
      FileStream1.ReadBuffer(StrTwo[1], Len1);

      FileStream1.ReadBuffer(Len1, SizeOf(Len1));
      if Len1 > 2048 then Exit;
      SetLength(StrThree, Len1);
      FileStream1.ReadBuffer(StrThree[1], Len1);

      finally
      FileStream1.Free;;
      end;
    WriteLn(StrOne);
    WriteLn(StrTwo);
    WriteLn(StrThree);
    end;

    We can read all three strings out of this file, because we recorded the number of bytes (length of the string) for each Data Segment, so we could read that out of the file and know how many bytes to read into that variable. You should notice that I put a test for the Len1 variable after each Len1 file read,

    if Len1 > 2048 then Exit;

    You should consider doing this when you first are trying these file read methods, and you can have coding or sequence errors in your file write or read procedures, since the Buffer is an UnTyped variable, you may not get warnings about variable or syntax code errors, so if your file position is incorrect for a Len1 FileStream Read, you might get a Large value (incorrect read) in the Len1 variable, and try and set the length of the string to an incorrect amount.

    This string file example uses three strings, and since we know how the file was written (using 3 strings, with the length of each string recorded as a Cardinal), we can read the file. If you need a method to write and read a file that does Not have a fixed number of strings, you can use Dynamic Array, to hold your strings (or a TStringList) then write and read this array to the FileStream.

    Writing and Reading Dynamic Arrays of Non-Fixed Size Variables

    Dynamic Array is a very useful because you can set it's Size with SetLength( ). In this next example, a Dynamic Array of String (arrayString) is used and it's Length is set to 4, but you can set it's length to another amount and the code will still work. This uses the same methods to write and read the strings as the last example, recording the length of the string as a cardinal value, but since the amount of Strings in the Array can change, we need to record the Number of elements in the Array. You get the number of strings in the Array with the Length function and then write this number as the first thing in the FileStream. Then use a FOR loop to write all of the strings and their length to the file.

    Simba Code:
    procedure WriteToFile3;
    var
    FileStream1: TFileStream;
    arrayString: Array of String;
    Len1, c: Cardinal;
    begin
    SetLength(arrayString, 4);
    arrayString[0] := 'First string in this Array';
    arrayString[1] := 'the Second Array string';
    arrayString[2] := 'String number three of this Array';
    arrayString[3] := 'this is the fourth String';

    FileStream1.init('D:\arraystr.msf',
                     $FF00 or $0001 or $0020);

    try
      c := Length(arrayString);
      FileStream1.WriteBuffer(c, SizeOf(c));
      FOR c := 0 to High(arrayString) do
        begin
        Len1 := Length(arrayString[c]);
        FileStream1.WriteBuffer(Len1, SizeOf(Len1));
        if Len1 > 0 then
        FileStream1.WriteBuffer(arrayString[c][1], Len1);
        end;
      finally
      FileStream1.Free;
      end;
    end;

    This will write all of the strings in the arrayString to file, it is Important that you place the "Length" of the arrayString Array as the first thing in your file
    Simba Code:
    c := Length(arrayString);
    FileStream1.Write(c, SizeOf(c));

    So when you Read this file you can set the "Length" of your array to hold all of the strings in the file. Here is the code to read this file:

    Simba Code:
    procedure ReadFromFile3;
      var
      FileStream1: TFileStream;
      arrayString: Array of String;
      Len1, c: Cardinal;

    begin
    FileStream1.Init('D:\arraystr.msf',
                     $0000 or $0020);
    try
      FileStream1.ReadBuffer(Len1, SizeOf(Len1));
      if (Len1 = 0) or (Len1 > 1000) then Exit;
      SetLength(arrayString, Len1);
      FOR c := 0 to Len1-1 do
        begin
        FileStream1.ReadBuffer(Len1, SizeOf(Len1));
        if Len1 > 2048 then Exit;
        SetLength(arrayString[c], Len1);
        FileStream1.ReadBuffer(arrayString[c][1], Len1);
        end;
      finally
      FileStream1.Free;;
      end;
    WriteLn(arrayString[0]);
    WriteLn(arrayString[High(arrayString)]);
    end;

    As you can see, the first thing that is Read from FileStream1 is the Len1 variable (Cardinal) and it is tested to see if it is zero or larger than 1000. If the value of Len1 passes this safety test, then the Length of the arrayString Array is set to the value of Len1. Now that we have enough room in our array for all of the strings, we can use a FOR loop to read the string length, and set the length of each string and then read the string's charaters from the file. You should notice that any NON-Fixed values (like the Length of the Array, or the Length of the Strings) need to be written to the File along with the Data that is in your variables. Lets look at some methods to use for saving another Non-Fixed size variable, a Bitmap, to a Multi-Data File.

    You should have enough TFileStream knowledge to start and build your own custom Multi-Data files, and use different write and read methods to build your file in a way that will have the options you need for your program. There are Many, Many more ways to place Data into a file and read data out of a file, so you will need to take the time to do this with your own files and experiment with making files that store the data you need for your program. Remember that you will need to know what data is in the file, the position and byte size of that data in the file. If you are able to make a list of all the different pieces of data (variables) and their "types" that your program needs to store and read in a file, you should be able to figure out how to arrange the data segments, then write and read them for a file. There is no way around it, to get to know about file writting and reading, you will need to do it, you will need to take the time to make several different files and read these files. For most this is not an easy thing to understand, since it has so many factors that can change.

    The end.

    Well, this original article I was found in Google a many years ago, and that is very helpful for me. I was reworked that for simba's lape, and all examples is work as it is written in the article.

    Happy scripting!

    Cheers,
    Cynic.
    Per aspera ad Astra!
    ----------------------------------------
    Slow and steady wins the race.

  4. #4
    Join Date
    Oct 2006
    Location
    Netherlands
    Posts
    3,285
    Mentioned
    105 Post(s)
    Quoted
    494 Post(s)

    Default

    Stickified!
    This. Is. Awesome.
    Working on: Tithe Farmer

  5. #5
    Join Date
    Mar 2012
    Location
    127.0.0.1
    Posts
    3,383
    Mentioned
    95 Post(s)
    Quoted
    717 Post(s)

    Default

    Wow.

    10/10

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •