XBasic File I/O aspects

For as far as XBasic file I/O goes you might fall into severe traps of making beginner-mistakes.

To help you out you can download a source-file demonstration here that for sure works.

The demonstration gives you the basics to read text and binary files in two methods and this should be enough to practice reading/writing various data-base formats or designing your own
The contents have detailed explanation about what is done, below is the extract with a graphical representation of some of the details described.

Success!
 

 

'
'
' ####################
' #####  PROLOG  #####
' ####################
'
PROGRAM "File read write Demo"  ' 1-8 char program/file name without .x or any .extent
VERSION "1.0000"    ' version number - increment before saving altered program
'
' This sourcefile belongs to Course F_01 which can be found on the tutorial site http://xbasicdocs.freeservers.com
'
' This small file demonstrates various File I/O aspects of XBasic.
'
' Not treated:Serial / Parallel I/O, Networking
'
'
' ******  Comment libraries in/out as needed  *****
'
'       IMPORT  "xma"   ' Math library     : SIN/ASIN/SINH/ASINH/LOG/EXP/SQRT...
'       IMPORT  "xcm"   ' Complex library  : complex number library  (trig, etc)
        IMPORT  "xst"   ' Standard library : required by most programs
'       IMPORT  "xgr"   ' GraphicsDesigner : required by GuiDesigner programs
'       IMPORT  "xui"   ' GuiDesigner      : required by GuiDesigner programs
'

DECLARE FUNCTION  Entry ()
DECLARE FUNCTION  FileOpen (FileName$, FileMode)
DECLARE FUNCTION  FileClose (FileHandle)
DECLARE FUNCTION  ReadArray (FileName$, FileHandle, ReadArray$[])
DECLARE FUNCTION  WriteArray (FileName$, FileHandle, WriteArray$[])
DECLARE FUNCTION  ReadData (FileHandle, BitAlignment, Record)
DECLARE FUNCTION  WriteData (FileHandle, BitAlignment, Record)


TYPE DATA32ALIGNED              ' All types below represent a 32-bit integer OR represent a chained 32 bit integer (4 x UBYTE = 4 bytes)
        STRING  *       19      .Value0
        XLONG                                   .Value1
        XLONG                                   .Value2
        USHORT                          .Value31
        USHORT                          .Value32
        UBYTE                                   .Value41
        UBYTE                                   .Value42
        UBYTE                                   .Value43
        UBYTE                                   .Value44
END TYPE

TYPE DATAXUNALIGNED     ' All types below may or may not represent a 32-bit integer yet there aren't any 32-bit chained types
        STRING  *       19      .Value0
        USHORT                          .Value11
        UBYTE                                   .Value21
        XLONG                                   .Value3
        UBYTE                                   .Value41
END TYPE
'
'
' ######################
' #####  Entry ()  #####
' ######################
'
'
' Demonstration of writing / reading string-arrays in two methods and composite variables / arrays
'
'
FUNCTION  Entry ()
        XstClearConsole()

        FileName$ = "MyTestFile.dbt"
        FileMode = $$RWNEW      ' Delete the existing file each time you open it, BEWARE!:Take care you don't have a file with that name that you want to keep!!!


        ' ###############################
        ' # Reading / writing variables #
        ' ###############################
        ' Reading and writing simple variables do not really have anything to it.
        ' I assume this is what you tested first and most probably succeeded in doing it.
        ' If not below a quick-reference:
        ' Simple typed variables will just be saved in a direct spot of the file sizing the amount of chars that the type takes:
        ' WRITE [FileHandle], A$, B$, C!, D!!, A@, B@@, F#, G##
        ' The above will write an unaligned structured file output of various typed variables, no holes will be added by XBasic
        '
        ' With reading the order goes the same except that strings have to be filled with the amount of characters you want to read:
        ' A$ = CHR$(00, 20)
        ' B$ = CHR$(00, 38)
        ' READ [FileHandle], A$, B$, A@, B@@, F, G
        ' Reads the first 20 characters into A$ and the second 38 characters into B$ followed by an SBYTE, a UBYTE and two XLONG
        ' variables., all together this record is 68 characters long
        ' Now for the real work....
        '

        ' ###################################
        ' # Reading / writing string-arrays #
        ' ###################################
        ReadFile$ = "Tutor_File_F_01.x"

        ' We open the textfile for the old fashioned QB way, for XBasic this is not nessesary however i want to demonstrate both
        ' methods.
        FileHandle = FileOpen(ReadFile$, $$RD)

        ' We give the order to read this source-file
        ReadArray (ReadFile$, FileHandle, @ReadArray$[])

        ' Dump it to the console
        FOR X = 0 TO UBOUND(ReadArray$[])
                PRINT ReadArray$[X]
        NEXT X

        ' Then we close this sourcefile to enable attachment of another file to the filehandle:
        FileClose(FileHandle)

        ' Opening a new file (deleting the existing one) and for saving stringarrays this is also not necessary in XBasic but only
        ' for comparement demonstration between the old QB way and the XBasic way.
        FileHandle = FileOpen(FileName$, $$WRNEW)

        ' We write the contents of the readarray to the new filename.
        WriteArray (FileName$, FileHandle, @ReadArray$[])

        ' Close the new file
        FileClose(FileHandle)

        PRINT                   ' If you want to check the contents then set a breakpoint to this line and open the MyTestFile.dbt inside a text-
                                                ' editor to figure what has been written to it.


        ' ###############################
        ' # Reading / writing databases #
        ' ###############################

 

        ' Unaligned Vs Aligned:
        ' Unaligned databases are databases that have a structure that cannot be read without tricks in the filesystem you want it
        ' to read in.
        ' Aligned databases are written in and for the platform you desire to read them in.
        ' If you want to read a 32-bit structured database in Win9x, your database is aligned since Win9x is 32-bit compliant.
        ' If you want to read a DOS structured database in Win9x then this database MAY BE unaligned since you may miss 16 bits
        ' per block to read the file properly.
        ' MAY BE is translated into values which can be organised in a way that a 32-bit chain is not formed.
        ' When this is the case that database is rawly unaligned, if all record elements only form 32-bit chains then you may
        ' accidently be able to read the database without problems in a 32-bit operating system.

        ' 32-bit chain:
        ' A 32-bit chain are a cluster of equal datatype values that represent a 32-bit value, examples are:
        ' 4 UBYTEs, 4 SBYTEs
        ' 2 SSHORTs, 2 USHORTs
        '
        ' 2 ULONGs would be an example of a 64-bit chain since an ULONG is 32-bit.
        ' An XLONG can be either 32 bit or 64 bit. When you would use XBasic on a RISC machine, an XLONG would be 64-bit!


        ' An 8-bit structured database does not contain holes in recordstructures yet is not aligned for use in higer bit-type
        ' systems.
        ' An unaligned 16-bit structured database can contain 8-bit holes in recordstructures, this makes them 16-bit unaligned.
        ' An unaligned 32-bit structured database may contain 8-bit holes, 16-bit holes or both.
        ' An unaligned raw structured database will not contain compensation holes and has to be read the same method
        ' like we need to read an 8-bit structured database
        '
        ' -A good example of an unaligned database is a 16-bit database that doesn't use 2 clustered SHORT typed and/or 4 clustered
        ' BYTE typed values in a record while a 32-bit environment has to read it yet 8-bit holes are used to fill out the missing
        ' 8-bit part of a type (when discussing a program saving in 16-bit mode).
        '
        ' -A good example of a 16-bit structured aligned database is a database that does use 2 clustered SHORT typed and/or 4
        ' clustered BYTE typed values in a record.
        '
        ' -A good example of an unaligned 32-bit database is a database that has non-clustered typed bytes but types are saved
        ' with the additional amount of bytes to fill out a 32-bit value per record-component.
        '
        ' -A good example of an aligned 32-bit database has clustered typed bytes so therefore does not contain "holes" in the
        ' record structure.





        ' Dealing with various (sub)situations:
        ' In the real world we can deal with various types of databases but the situations from where they come or for what they
        ' serve are few...
        ' We can be in a couple of situations:
        ' Situation A
        ' One needs to read a database format written on a platform using a different bit-alignment structure than current;
        ' Windows 32-bit Vs. DOS 16-bit, or Windows 32-bit Vs. Commodore Basic 8-bit
        ' The sub-situation can be
        ' 1- that either filestructures have to be converted from one and/or to the other system
        ' 2- that the filestructure has to be kept intact for using on both platforms (cross/multiple-referencable databases)
        ' Situation 1 only requires an interface that reads the unaligned format once and just writes it back to the aligned format.
        ' Once this is done you only need the routine to load the aligned format.
        ' Situation 2 requires an interface that will as well read as write in that particular format.
        ' Only one of the platforms will keep the comfort of ease and effective reading but the other will act slower because
        ' of the multiple translation steps it has to make.
        '
        ' Situation B
        ' One needs to design a database format or read/write to an existing database format for one platform
        ' In this case there is no subsituation, you can just structure your databases any way you like it though you have
        ' to save them in one pass. Well this last thing is actually automaticly the first choosen option because the other method
        ' requires more code and is by far less efficient.

 


        ' Conclusion:
        ' A good database design is aligned to serve it's maximum bit-type, in case of XBasic this is 64-bit, and considering the
        ' future you may also put up chains that cover 64- to 256-bit database structures (or more).
        ' In that case your databases can always be read in future platforms and you don't have the hassle in another language
        ' converting your old fashioned databases.


        ' ####################################
        ' # 32-bit Aligned Database File I/O #
        ' ####################################

        ' Actually, the "DATA32ALIGNED" type design has a string-value of 19 characters which makes the design unaligned.
        ' XBasic fills up the 8-bit hole to align the recordstructure so the database itself will be written and read in an aligned
        ' 32-bit structure. That's what it is about here:the structure, not the DESIGN.

        FileHandle = FileOpen(FileName$, FileMode)

        PRINT "Writing aligned data"

        BitAlignment  = 32
        Record = -1 ' (All)
        WriteData (FileHandle, BitAlignment, Record)

        ' Write aligned record 10 to database
        BitAlignment  = 32
        Record = 10
        WriteData (FileHandle, BitAlignment, Record)

        SEEK(FileHandle, 0)             ' Reset filepointer

        PRINT "Reading aligned data"

        BitAlignment  = 32
        Record = 10
        ReadData (FileHandle, BitAlignment, Record)

        SEEK(FileHandle, 0)             ' Reset filepointer

        BitAlignment  = 32
        Record = -1 ' (All)
        ReadData (FileHandle, BitAlignment, Record)


        ' #####################################
        ' # X-bit Unaligned Database File I/O #
        ' #####################################

        FileClose(FileHandle)

        FileHandle = FileOpen(FileName$, FileMode)

        PRINT "Writing unaligned data"

        ' Let's write unaligned data to the file
        BitAlignment  = 0
        Record = -1 ' (All)
        WriteData (FileHandle, BitAlignment, Record)

        ' Write unaligned record 10 to database
        BitAlignment  = 0
        Record = 10
        WriteData (FileHandle, BitAlignment, Record)

        SEEK(FileHandle, 0)     ' reset filepointer

        PRINT "Reading unaligned data"

        ' Read unaligned record 10 to database
        BitAlignment  = 0
        Record = 10
        ReadData (FileHandle, BitAlignment, Record)

        SEEK(FileHandle, 0)             ' Reset filepointer

        ' Let's write unaligned data to the file
        BitAlignment  = 0
        Record = -1 ' (All)
        ReadData (FileHandle, BitAlignment, Record)


END FUNCTION
'
'
' #########################
' #####  FileOpen ()  #####
' #########################
'
FUNCTION  FileOpen (FileName$, FileMode)
' There are many ways you can open a file, below is the list containing values
' of which one or two has to be used in the method-parameter:
'
'  $$RD                  = 0x0000    ' read file
'  $$WR                  = 0x0001    ' write file
'  $$RW                  = 0x0002    ' read/write file
'  $$WRNEW               = 0x0003    ' write new file
'  $$RWNEW               = 0x0004    ' read/write new file
'
'  $$NOSHARE             = 0x0000    ' share file for none
'  $$RDSHARE             = 0x0010    ' share file for read
'  $$WRSHARE             = 0x0020    ' share file for write
'  $$RWSHARE             = 0x0030    ' share file for read & write

' The last four values need to be OR'ed with one of the above four to have it's effect worked out:
' OPEN ("/MyFile.txt", $$RWNEW OR $$RWSHARE)
' I guess you get the picture.

FileHandle = OPEN (FileName$, FileMode)

RETURN FileHandle       ' If file could not be opened, FileHandle will be -1.

' This function actually doubles the OPEN () command but it demonstrates how to use OPEN


END FUNCTION
'
'
' ##########################
' #####  FileClose ()  #####
' ##########################
'
FUNCTION  FileClose (FileHandle)
' There's nothing really spectacular about closing a file.
' There is a switch to close all files at once but remember that your console will be closed as well.
'
' Keep in mind that the first two file-handles are reserved for the console.
' Second closing the console can be done best by calling XstHideConsole() so you can bring it up again by using
' XstDisplayConsole()
'
'  $$CONSOLE                                             = 1                             ' CLOSE ($$CONSOLE)
'  $$ALL                 = -1        ' CLOSE ($$ALL)
'
' To close a file we only need a filehandle, though we will ofcourse check if it is higher than 2:

IF FileHandle > 2 THEN
        CLOSE (FileHandle)
ELSE
        RETURN -1       ' Error
END IF

END FUNCTION
'
'
' ##########################
' #####  ReadArray ()  #####
' ##########################
'
FUNCTION  ReadArray (FileName$, FileHandle, ReadArray$[])


        REDIM ReadArray$[-1]

        ' It doesn't matter how you would like to read your file, XBasic can do it the unefficient QB-way:

        DO WHILE NOT EOF(FileHandle)
                REDIM ReadArray$[UBOUND(ReadArray$[])+1]

                ReadArray$[UBOUND(ReadArray$[])] = INFILE$(FileHandle)
        LOOP

        CLOSE (FileHandle)

        ' Or the easy XB-way:
        Error = XstLoadStringArray  (FileName$, @ReadArray$[])

        FileHandle = OPEN (FileName$, $$RW)

        RETURN Error

END FUNCTION
'
'
' ###########################
' #####  WriteArray ()  #####
' ###########################
'
FUNCTION  WriteArray (FileName$, FileHandle, WriteArray$[])

        ' It doesn't matter how you would like to save your file, XBasic can do it the unefficient QB-way:

        FOR X = 0 TO UBOUND(WriteArray$[])
                Temp$ = WriteArray$[X]

                WRITE [FileHandle], Temp$
        NEXT X

        CLOSE (FileHandle)

        ' Or the easy XB-way:
        Error = XstSaveStringArray  (FileName$, @WriteArray$[])

        FileHandle = OPEN (FileName$, $$RW)

        RETURN Error

 

END FUNCTION
'
'
' #########################
' #####  ReadData ()  #####
' #########################
'
'
' Not much to it but handy to know how you can read aligned and unaligned data.
' An example of both (al/unal) is given in a single composite variable as well as in a composite array.
'
'
' XBasic documentation fragment:
' READ [ fileNumber ] , variables
' Read data from a disk file into variables. The first argument is the filenumber of the file to read from.
' The variables can be any combination of numeric variables, string variables, composite variables, composite variable
' components, or one dimensional arrays of any type except strings.
' The number of bytes read into each variable equals the data size of the variable. For example, one byte is read into SBYTE
' and UBYTE variables, two bytes into SSHORT and USHORT variables, etc.
' This is true even though simple variables shorter than 32-bit are held in memory as 32-bit or 64-bit values.
' In cases where the data size and storage size are not equal, the data size is read, zero or sign-extended to 32-bits or
' 64-bits, then saved in the storage variable.
' When data is read into a string variable, the number of bytes needed to fill the string are read directly into the string,
' overwriting the previous contents.
' When data is read into an array variable, the number of bytes needed to fill the array are read directly into the array,
' overwriting the previous contents.
'
' READ and WRITE are complementary statements.
'
' DIM a[31]
' DIM a#[63]
' a$ = NULL$(256)
' READ [ifile], a@@, b@@, c@@ ' read 1 byte each into UBYTE variables
' READ [ifile], a, b, c ' read 4 bytes each into XLONG variables
' READ [ifile], a!, b!, c! ' read 4 bytes each into SINGLE variables
' READ [ifile], a#, b#, c# ' read 8 bytes each into DOUBLE variables
' READ [ifile], a$ ' read 256 bytes to fill STRING a$
' READ [ifile], a[] ' read 128 bytes to fill XLONG a[31]
' READ [ifile], a#[] ' read 512 bytes to fill DOUBLE a#[63]
' READ [ofile], pixel ' read all bytes in composite variable
' READ [ofile], pixel.color ' read all bytes in component
' READ [ofile], name.kid[] ' read all bytes in component array
'
'
'
'
FUNCTION  ReadData (FileHandle, BitAlignment, ReadRecord)

        STATIC DATA32ALIGNED    DataType, DataTypeArray[]
        STATIC DATAXUNALIGNED   DataUType, DataUTypeArray[]


        PRINT "Reading data"
        PRINT "Record:";ReadRecord
        PRINT "BitAlignment:";BitAlignment

        SELECT CASE BitAlignment

                CASE 32         ' If we want to read the 32-bit aligned data we go to the special subroutine for it.
                        GOSUB Bit32

                CASE ELSE       ' Any other case will be interpreted as unaligned though anything over 32-bit is mostly a multiplex of 32.
                        GOSUB Unaligned

        END SELECT

SUB Bit32

        ' This is the aligned reader routine.
        ' Every 32-bit aligned chain can be read in one pass into a variable or an array without loosing bytes
        ' an XLONG, ULONG and SLONG are the default 32-bit types that automaticly form a chain.
        ' Other chains are 4 BYTES in one row, two SHORTS in one row. The larger types (64 bit) accommodate two LONG types
        ' so most likely this will read okay.

        SELECT CASE ReadRecord

                CASE -1         ' If -1 we want to read the whole database
                        REDIM DataTypeArray[113]

                        READ [FileHandle], DataTypeArray[]      ' Just read it.

                        FOR Cycle = 0 TO UBOUND(DataTypeArray[])        ' Now dump it.
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value0  -> >>"; DataTypeArray[Cycle].Value0; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value1  -> >>"; DataTypeArray[Cycle].Value1; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value2  -> >>"; DataTypeArray[Cycle].Value2; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value31 -> >>"; DataTypeArray[Cycle].Value31; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value32 -> >>"; DataTypeArray[Cycle].Value32; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value41 -> >>"; DataTypeArray[Cycle].Value41; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value42 -> >>"; DataTypeArray[Cycle].Value42; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value43 -> >>"; DataTypeArray[Cycle].Value43; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value44 -> >>"; DataTypeArray[Cycle].Value44; "<<"
                                PRINT

                        NEXT Cycle

                CASE ELSE       ' We want to read a specific record from the database
                        ' first we determine the size of a record,
                        ' Note: SIZE(MyCompositeType) will return a 32-bit devidable value, however we have written the database in one pass
                        ' in 32-bit mode, so our composite type has a complete size of 35 here but is saved as 36 bytes per records.
                        ' That's why we can just do a SIZE(DataType) and we will get the nearest upper 32-bit value that comes into it.
                        ' If the database was really 35 bytes per record we would need the unaligned reading method described below.
                        RecordSize = SIZE(DataType)

                        ' then we calculate where the record entrypoint of the file is
                        RecordEntry = (RecordSize * ReadRecord)

                        SEEK(FileHandle, RecordEntry)                                           ' we seek the entrypoint of the record in the dbase-file

                        READ [FileHandle], DataType                                             ' we then read the record from the file.

                        PRINT "Record "; ReadRecord;" loaded:"  ' Dump the record
                        PRINT "DataType.Value0  -> >>"; DataType.Value0; "<<"
                        PRINT "DataType.Value1  -> >>"; DataType.Value1; "<<"
                        PRINT "DataType.Value2  -> >>"; DataType.Value2; "<<"
                        PRINT "DataType.Value31 -> >>"; DataType.Value31; "<<"
                        PRINT "DataType.Value32 -> >>"; DataType.Value32; "<<"
                        PRINT "DataType.Value41 -> >>"; DataType.Value41; "<<"
                        PRINT "DataType.Value42 -> >>"; DataType.Value42; "<<"
                        PRINT "DataType.Value43 -> >>"; DataType.Value43; "<<"
                        PRINT "DataType.Value44 -> >>"; DataType.Value44; "<<"
                        PRINT

        END SELECT


END SUB


SUB Unaligned
        ' In this case we talk about type-chains that break the 32-bit range and most likely the structure does not contain holes.
        ' If you tie three UBYTEs and an XLONG then four bytes will be read, the first three will be put into the first three
        ' UBYTES, the fourth will be discarded and then the second four byte-cluster will be read into the XLONG.
        ' This above situation occurs if you would read an array or type in one pass to it's entry-address.
        ' To prevent this you have to read everything related to the size of the type-element so that XBasic knows that it
        ' has to read a specific byte.
        ' What it actually does is reading four bytes, taking out what it needs and then setting the fileposition back to the
        ' point of the byte that XBasic couldn't put in the type-element:
        '
        ' READ [FileHandle], MyType.MyUBYTE
        '
        ' will be translated into:
        '
        ' -> read 32-bit integer {read Xxlong}
        ' -> set curfilepos {curfilepos = start + 4}
        ' -> determine bytes needed {Type = UBYTE}
        ' -> take one byte from four {Xxlong << 24}
        ' -> reset curfilepos {curfilepos = curfilepos - 3}
        '
        ' This is a very wayround method but unfortunately, this is how Windows works.
        ' This also makes your application perform slower file-access due to the fact that Win32 has to make this conversion steps to
        ' keep track upon filepointers.
        ' This also explains why a 32-bit aligned FAT works faster simply because Win32 works this way and if it has to go back to
        ' 16-bit it can only do this by emulating it but it will be by far slower than when you would perform the action in REAL DOS
        ' since DOS is 16-bit oriented.
        ' The reason why 32-bit became a standard is to remove size-limits (harddisk space, memoryspace and that kind of stuff) and
        ' enable more chunk-space to obtain a better read/write performance and this last case is why a full 32 bit operational
        ' windows system performs a lot faster and better than when windows has to maintain 16-bit compatability mode.
        '
        ' okay, let's do it the hard way:
        '

        ' We want to know the filesize
        DataBaseSize = LOF(FileHandle)
        ' We also want to know the recordsize and here the pain starts:
        RecordSize = SIZE(DataUType.Value0) + SIZE(DataUType.Value11) + SIZE(DataUType.Value21) + SIZE(DataUType.Value3) + SIZE(DataUType.Value41)

        ' a simple SIZE(DataType) will return a 32-bit size value while the original type is not 32-bit so we also have to count
        ' up the complete record size by adding the real type-element sizes to the total.

        SELECT CASE ReadRecord  ' Do we want to read all or a particular record?

                CASE -1 ' We want to read all.

                        Cycles = (DataBaseSize / RecordSize) - 1        ' Calculate how many times we have to read a record.
                        REDIM DataUTypeArray[Cycles]    ' Make room for the database to read

                        FOR Cycle = 0 TO Cycles ' Read and dump the contents
                                ' Sorry, reading composite arrays on elemental level is not possible in XBasic
                                ' So we have to use a composite variable instead
                                READ [FileHandle], DataUType.Value0
                                READ [FileHandle], DataUType.Value11
                                READ [FileHandle], DataUType.Value21
                                READ [FileHandle], DataUType.Value3
                                READ [FileHandle], DataUType.Value41
                                DataUTypeArray[Cycle].Value0    = DataUType.Value0
                                DataUTypeArray[Cycle].Value11   = DataUType.Value11
                                DataUTypeArray[Cycle].Value21   = DataUType.Value21
                                DataUTypeArray[Cycle].Value3    = DataUType.Value3
                                DataUTypeArray[Cycle].Value41   = DataUType.Value41
                                PRINT "DataUTypeArray[";STRING$(Cycle);"].Value0  -> >>"; DataUTypeArray[Cycle].Value0; "<<"
                                PRINT "DataUTypeArray[";STRING$(Cycle);"].Value11 -> >>"; DataUTypeArray[Cycle].Value11; "<<"
                                PRINT "DataUTypeArray[";STRING$(Cycle);"].Value21 -> >>"; DataUTypeArray[Cycle].Value21; "<<"
                                PRINT "DataUTypeArray[";STRING$(Cycle);"].Value3  -> >>"; DataUTypeArray[Cycle].Value3; "<<"
                                PRINT "DataUTypeArray[";STRING$(Cycle);"].Value41 -> >>"; DataUTypeArray[Cycle].Value41; "<<"
                                PRINT

                        NEXT Cycle

                CASE ELSE
                                RecordEntry = RecordSize * ReadRecord   ' We go and read a particular record

                                SEEK(FileHandle, RecordEntry)                                   ' we seek the entrypoint of the record in the dbase-file

                                ' Again read and dump the record
                                READ [FileHandle], DataUType.Value0
                                READ [FileHandle], DataUType.Value11
                                READ [FileHandle], DataUType.Value21
                                READ [FileHandle], DataUType.Value3
                                READ [FileHandle], DataUType.Value41
                                PRINT "Record "; ReadRecord;" loaded:"
                                PRINT "DataUType.Value0  -> >>"; DataUType.Value0; "<<"
                                PRINT "DataUType.Value11 -> >>"; DataUType.Value11; "<<"
                                PRINT "DataUType.Value21 -> >>"; DataUType.Value21; "<<"
                                PRINT "DataUType.Value3  -> >>"; DataUType.Value3; "<<"
                                PRINT "DataUType.Value41 -> >>"; DataUType.Value41; "<<"
                                PRINT
        END SELECT

END SUB


END FUNCTION
'
'
' ##########################
' #####  WriteData ()  #####
' ##########################
'
'
' Here we are going to write 32-bit aligned or unaligned data.
' If we want to have our non-32-bit aligned record-format written the way we or someone else designed it we need to do
' a couple of extra things while we don't need to do much hassle to save 32-bit aligned databases.
' An example of both (al/unal) is given in a single composite variable as well as in a composite array.
'
'
FUNCTION  WriteData (FileHandle, BitAlignment, WriteRecord)

        STATIC DATA32ALIGNED    DataType, DataTypeArray[]
        STATIC DATAXUNALIGNED   DataUType, DataUTypeArray[]

        PRINT "Database generating and saving...."
        PRINT   "Bitalignment"; BitAlignment
        PRINT "Current record to write:"; WriteRecord

        SELECT CASE BitAlignment

                CASE 32         ' If we want to read the 32-bit aligned data we go to the special subroutine for it.
                        GOSUB Bit32

                CASE ELSE       ' Any other case will be interpreted as unaligned though anything over 32-bit is mostly a multiplex of 32.
                        GOSUB Unaligned

        END SELECT

SUB Bit32

        ' This is the aligned writer routine.
        ' Every 32-bit aligned chain can be read in one pass into a variable or an array without loosing bytes
        ' an XLONG, ULONG and SLONG are the default 32-bit types that automaticly form a chain.
        ' Other chains are 4 BYTES in one row, two SHORTS in one row. The larger types (64 bit) accommodate two LONG types
        ' so most likely this will read okay.

        ' We first generate a phantom database with figures and chars and then we save the whole thing.

        REDIM DataTypeArray[113]

        FOR Cycle = 0 TO 113
                DataTypeArray[Cycle].Value0 = CHR$(Cycle+33, 19)
                DataTypeArray[Cycle].Value1     = (Cycle+1) * 1024
                DataTypeArray[Cycle].Value2     = (Cycle+1) * 1124
                DataTypeArray[Cycle].Value31 = (Cycle+1) * 128
                DataTypeArray[Cycle].Value32 = (Cycle+1) * 256
                DataTypeArray[Cycle].Value41 = (Cycle+1) * 2
                DataTypeArray[Cycle].Value42 = ((Cycle+1) * 2) + 1
                DataTypeArray[Cycle].Value43 = ((Cycle+1) * 2) + 2
                DataTypeArray[Cycle].Value44 = ((Cycle+1) * 2) + 3
        NEXT Cycle

        SELECT CASE WriteRecord

                CASE -1         ' If -1 we want to read the whole database
                        WRITE [FileHandle], DataTypeArray[]     ' Just Write it.
                        ' NOTE, our recordsize is 35 bytes per record, however XBasic aligns it to 36 per record (adding an 8-bit hole) and
                        ' stores it that way.
                        ' In this matter we won't have troubles reading it back the same way we have written it to a file in Windows.
                        ' Even if our datatype structure would be very unaligned, the saving method from within XBasic will always be
                        ' enlarged to a 32-bit compatible record-size per composite type row (in an array) or per composite type variable.
                        ' However if we would need to write the datastructure in 35 bytes per record for another application (operating under
                        ' DOS or just requiring a 35 bytes recordsize structure) then we will need the unaligned writing method shown in the
                        ' subroutine below.

                        FOR Cycle = 0 TO UBOUND(DataTypeArray[])        ' Now dump it.
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value0  -> >>"; DataTypeArray[Cycle].Value0; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value1  -> >>"; DataTypeArray[Cycle].Value1; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value2  -> >>"; DataTypeArray[Cycle].Value2; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value31 -> >>"; DataTypeArray[Cycle].Value31; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value32 -> >>"; DataTypeArray[Cycle].Value32; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value41 -> >>"; DataTypeArray[Cycle].Value41; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value42 -> >>"; DataTypeArray[Cycle].Value42; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value43 -> >>"; DataTypeArray[Cycle].Value43; "<<"
                                PRINT "DataTypeArray[";STRING$(Cycle);"].Value44 -> >>"; DataTypeArray[Cycle].Value44; "<<"
                                PRINT

                        NEXT Cycle

                CASE ELSE       ' We want to read a specific record form the database
                        RecordSize = SIZE(DataType)                                                     ' first we determine the size of a record
                        RecordEntry = RecordSize * WriteRecord          ' then we calculate where it starts

                        SEEK(FileHandle, RecordEntry)                                           ' we seek the entrypoint of the record in the dbase-file

                        X = WriteRecord / (113 / 2)

                        ' We fill the record
                        DataType.Value0 = CHR$(X+33, 19)
                        DataType.Value1 = (X+1) * 1024
                        DataType.Value2 = (X+1) * 1124
                        DataType.Value31 = (X+1) * 128
                        DataType.Value32 = (X+1) * 256
                        DataType.Value41 = (X+1) * 2
                        DataType.Value42 = ((X+1) * 2) + 1
                        DataType.Value43 = ((X+1) * 2) + 2
                        DataType.Value44 = ((X+1) * 2) + 3

                        WRITE [FileHandle], DataType                                            ' we then save the record to the file at it's position.

                        PRINT "Record "; WriteRecord;" saved:"  ' Dump the record
                        PRINT "DataType.Value0  -> >>"; DataType.Value0; "<<"
                        PRINT "DataType.Value1  -> >>"; DataType.Value1; "<<"
                        PRINT "DataType.Value2  -> >>"; DataType.Value2; "<<"
                        PRINT "DataType.Value31 -> >>"; DataType.Value31; "<<"
                        PRINT "DataType.Value32 -> >>"; DataType.Value32; "<<"
                        PRINT "DataType.Value41 -> >>"; DataType.Value41; "<<"
                        PRINT "DataType.Value42 -> >>"; DataType.Value42; "<<"
                        PRINT "DataType.Value43 -> >>"; DataType.Value43; "<<"
                        PRINT "DataType.Value44 -> >>"; DataType.Value44; "<<"
                        PRINT

        END SELECT


END SUB


SUB Unaligned
        ' In this case we talk about type-chains that break the 32-bit range....
        ' If you tie three UBYTEs and an XLONG then four bytes will be read, the first three will be put into the first three
        ' UBYTES, the fourth will be discarded and then the second four byte-cluster will be read into the XLONG.
        ' This above situation occurs if you would read an array or type in one pass to it's entry-address.
        ' To prevent this you have to read everything related to the size of the type-element so that XBasic knows that it
        ' has to read a specific byte.
        ' What it actually does is reading four bytes, taking out what it needs and then setting the fileposition back to the
        ' point of the byte that XBasic couldn't put in the type-element:
        '
        ' WRITE [FileHandle], MyType.MyUBYTE
        '
        ' will be translated into:
        '
        ' -> write 32-bit integer {read Xxlong}
        ' -> set curfilepos {curfilepos = start + 4}
        ' -> determine bytes needed {Type = UBYTE}
        ' -> take one byte from four {Xxlong << 24}
        ' -> reset curfilepos {curfilepos = curfilepos - 3}
        '
        ' This is a very wayround method but unfortunately, this is how Windows works.
        ' This also makes your application perform slower file-access due to the fact that Win32 has to make this conversion steps to
        ' keep track upon filepointers.
        ' This also explains why a 32-bit aligned FAT works faster simply because Win32 works this way and if it has to go back to
        ' 16-bit it can only do this by emulating it but it will be by far slower than when you would perform the action in REAL DOS
        ' since DOS is 16-bit oriented.
        ' The reason why 32-bit became a standard is to remove size-limits (harddisk space, memoryspace and that kind of stuff) and
        ' enable more chunk-space to obtain a better read/write performance and this last case is why a full 32 bit operational
        ' windows system performs a lot faster and better than when windows has to maintain 16-bit compatability mode.
        '
        ' okay, let's do it the hard way:
        '

        ' We also want to know the recordsize and here the pain starts:
        RecordSize = SIZE(DataUType.Value0) + SIZE(DataUType.Value11) + SIZE(DataUType.Value21) + SIZE(DataUType.Value3) + SIZE(DataUType.Value41)
        ' We want to know the filesize
        DataBaseSize = RecordSize * 114

        ' a simple SIZE(DataType) will return a 32-bit size value while the original type is not 32-bit so we also have to count
        ' up the complete record size by adding the real type-element sizes to the total.

        SELECT CASE WriteRecord ' Do we want to write all or a particular record?

                CASE -1 ' We want to write all.

                        Cycles = (DataBaseSize / RecordSize) - 1        ' Calculate how many times we have to read a record.
                        REDIM DataUTypeArray[113]       ' Make room for the database to generate

                        FOR Cycle = 0 TO 113
                                DataUTypeArray[Cycle].Value0 = CHR$(Cycle+33, 19)
                                DataUTypeArray[Cycle].Value11   = (Cycle+1) * 256
                                DataUTypeArray[Cycle].Value21   = (Cycle+1) * 2
                                DataUTypeArray[Cycle].Value3    = (Cycle+1) * 1124
                                DataUTypeArray[Cycle].Value41   = (Cycle+1) * 2
                        NEXT Cycle

                        FOR Cycle = 0 TO Cycles ' Write and dump the contents
                                DataUType.Value0        = DataUTypeArray[Cycle].Value0
                                DataUType.Value11       =       DataUTypeArray[Cycle].Value11
                                DataUType.Value21       =       DataUTypeArray[Cycle].Value21
                                DataUType.Value3        = DataUTypeArray[Cycle].Value3
                                DataUType.Value41       =       DataUTypeArray[Cycle].Value41
                                WRITE [FileHandle], DataUType.Value0
                                WRITE [FileHandle], DataUType.Value11
                                WRITE [FileHandle], DataUType.Value21
                                WRITE [FileHandle], DataUType.Value3
                                WRITE [FileHandle], DataUType.Value41

                                PRINT "DataUTypeArray[";STRING$(Cycle);"].Value0  -> >>"; DataUTypeArray[Cycle].Value0; "<<"
                                PRINT "DataUTypeArray[";STRING$(Cycle);"].Value11 -> >>"; DataUTypeArray[Cycle].Value11; "<<"
                                PRINT "DataUTypeArray[";STRING$(Cycle);"].Value21 -> >>"; DataUTypeArray[Cycle].Value21; "<<"
                                PRINT "DataUTypeArray[";STRING$(Cycle);"].Value3  -> >>"; DataUTypeArray[Cycle].Value3; "<<"
                                PRINT "DataUTypeArray[";STRING$(Cycle);"].Value41 -> >>"; DataUTypeArray[Cycle].Value41; "<<"
                                PRINT

                        NEXT Cycle

                CASE ELSE
                                RecordEntry = RecordSize * WriteRecord  ' We go and save a particular record

                                X = WriteRecord / (113 / 2)

                                DataUType.Value0 = CHR$(X+33, 19)
                                DataUType.Value11       = (X+1) * 256
                                DataUType.Value21       = (X+1) * 2
                                DataUType.Value3        = (X+1) * 1124
                                DataUType.Value41       = (X+1) * 2

                                SEEK(FileHandle, RecordEntry)                                   ' we seek the entrypoint of the record in the dbase-file

                                ' Again write and dump the record
                                WRITE [FileHandle], DataUType.Value0
                                WRITE [FileHandle], DataUType.Value11
                                WRITE [FileHandle], DataUType.Value21
                                WRITE [FileHandle], DataUType.Value3
                                WRITE [FileHandle], DataUType.Value41
                                PRINT "Record "; WriteRecord;" saved:"
                                PRINT "DataUType.Value0  -> >>"; DataUType.Value0; "<<"
                                PRINT "DataUType.Value11 -> >>"; DataUType.Value11; "<<"
                                PRINT "DataUType.Value21 -> >>"; DataUType.Value21; "<<"
                                PRINT "DataUType.Value3  -> >>"; DataUType.Value3; "<<"
                                PRINT "DataUType.Value41 -> >>"; DataUType.Value41; "<<"
                                PRINT
        END SELECT

END SUB
END FUNCTION
END PROGRAM