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