Simple file I/O issue

General FreeBASIC programming questions.
skystrick
Posts: 104
Joined: May 19, 2013 23:25
Location: Florida

Simple file I/O issue

Postby skystrick » May 19, 2013 23:33

Hey all, I'm a new convert to freebasic from QB 4.5!

I am having just one issue though: files that I create in freebasic, cannot seem to be read by freebasic.

For example:

LET A = 1
LET B$ = "HELLO"
LET C$ = "TESTING"
OPEN "FILE.DAT" FOR RANDOM AS #1
PUT #1, 1, A
PUT #1, 2, B#
PUT #1, 3, C$
CLOSE #1


Should create a three-line file which looks like this:

1
HELLO
TESTING

However, it does not. When I run a program to input the data from the saved file:

OPEN "FILE.DAT" FOR RANDOM AS #1
GET #1, 1, A
GET #1, 2, B$
GET #1, 3, C$
PRINT A
PRINT B
PRINT C
CLOSE #1

That program should print, on my screen:

1
HELLO
TESTING

But it does not.

It manages to input the integer variable (A) from of line #1, but it cannot pull the string variables (B$ and C$) out of lines 2 and 3.

So my screen looks like this:

1


And that's it.

If it makes a difference, I'm running freebasic on Ubuntu Linux.

Thanks!
counting_pine
Site Admin
Posts: 6229
Joined: Jul 05, 2005 17:32
Location: Manchester, Lancs

Re: Simple file I/O issue

Postby counting_pine » May 20, 2013 2:00

Hi skystrick,
It looks to me like FB might have a problem with RANDOM file I/O.
If I compile with error checking (fbc -exx), then I get a file I/O error on 'PUT #1, 1, A'.

I think it's because for some reason we require PUTs in RANDOM mode to be the same length as the record length, which is 128 bytes by default.

If it will help, we can suggest some workarounds. Do you need to keep your data saved in the same file format as before?
skystrick
Posts: 104
Joined: May 19, 2013 23:25
Location: Florida

Re: Simple file I/O issue

Postby skystrick » May 20, 2013 2:30

counting_pine wrote:Hi skystrick,
It looks to me like FB might have a problem with RANDOM file I/O.
If I compile with error checking (fbc -exx), then I get a file I/O error on 'PUT #1, 1, A'.

I think it's because for some reason we require PUTs in RANDOM mode to be the same length as the record length, which is 128 bytes by default.

If it will help, we can suggest some workarounds. Do you need to keep your data saved in the same file format as before?


Okay, here's what I need to be able to do:

I need to be able to output the content of variables to files, and then retrieve that content from files. Any given file will contain variables of various types (integers, strings, etc.) and varying lengths. Also, I make use of user-defined types which contain sub-records of varying lengths and variable types; for example:

Code: Select all

TYPE TRANSACTION
   TRANSEQ AS INTEGER
   TRANDATE AS STRING * 8
   PAYEE AS STRING * 20
   CHECK AS STRING * 4
   TRANTYPE AS STRING * 1
   AMOUNT AS DOUBLE
   RUNBAL AS DOUBLE
END TYPE
DIM EXAMPLE AS TRANSACTION


Under QB, I could store the record EXAMPLE -- containing all 7 sub-records, which vary in length and type -- as a single record in a file (i.e. PUT #1, 1, EXAMPLE), which would store the whole thing as record #1 in whatever file I have open for RANDOM as #1, and then retrieve it as a whole unit and work with the sub-units (i.e. "LOCATE 10, 15: PRINT EXAMPLE.PAYEE").

I just need to know how to accomplish the same ends in freebasic. I am used to using RANDOM mode, because BINARY requires that I know the exact length of every record before the one I want, so that I know which byte to point to in the file, and becomes a pain in the butt when dealing with multi-part records such as the user-defined type shown above. It was always just so simple, quick, and easy to just be able to say "retrieve record X from file Y and drop into into the user-defined type TRANSACTION as variable EXAMPLE" with the TYPE, DIM, and GET/PUT statements.

I'm porting a program I wrote under QB, and I've gotten everything to work except this file I/O issue, and the LPRINT command.

Thanks again!
Richard
Posts: 3038
Joined: Jan 15, 2007 20:44
Location: Australia

Re: Simple file I/O issue

Postby Richard » May 20, 2013 4:05

This emulates the Lprint command by writing to a file.
You can then later Open, Edit and Print that file under OS control.
Make sure that you use Freefile for all files, or hard-code printer as a fixed file number.

Code: Select all

'===================================================================
' Lprint and Lprint Using to a file
'===================================================================
' At the start of your program
Dim As String printfile = "lineprint.txt"
Dim As Integer printer = Freefile
Open printfile For Output As #printer
#UnDef Lprint
#Define Lprint Print #printer,

'-------------------------------------------------------------------
' within your program

Lprint "Hello world."

Lprint "Goodbye world."

'-------------------------------------------------------------------
' At the end of the program close the file
Close #printer

'===================================================================
Print "Normal exit."
Sleep
'===================================================================
 
counting_pine
Site Admin
Posts: 6229
Joined: Jul 05, 2005 17:32
Location: Manchester, Lancs

Re: Simple file I/O issue

Postby counting_pine » May 20, 2013 4:27

I think the most foolproof way to do what you want is to create routines to get/put your data.
The Put routine will pack all the data into a string of a known size, then Put that string.
The Get routine Gets into a string of a known size, and then unpacks all the fields and stores them in the type.

This is necessary to bypass a handful of size/alignment issues that FB has when creating types as compared to QB, particularly with fixed-length strings.

Code: Select all

TYPE TRANSACTION            '' element size, offset
   TRANSEQ AS INTEGER       ''  1, 2
   TRANDATE AS STRING' * 8  ''  3, 8
   PAYEE AS STRING' * 20    '' 11,20
   CHECK AS STRING' * 4     '' 31, 4
   TRANTYPE AS STRING' * 1  '' 35, 1
   AMOUNT AS DOUBLE         '' 36, 8
   RUNBAL AS DOUBLE         '' 44, 8
END TYPE                    '' total size: 52-1 = 51

DIM EXAMPLE AS TRANSACTION

SUB PUTTRANSACTION(FILENUM AS INTEGER, START AS LONG, T AS TRANSACTION)
    DIM REC AS STRING*51

    REC = SPACE$(51)
    MID$(REC,  1, 2) = MKI$(T.TRANSEQ)
    MID$(REC,  3, 8) = T.TRANDATE
    MID$(REC, 11, 20) = T.PAYEE
    MID$(REC, 31, 4) = T.CHECK
    MID$(REC, 35, 1) = T.TRANTYPE
    MID$(REC, 36, 8) = MKD$(T.AMOUNT)
    MID$(REC, 44, 8) = MKD$(T.RUNBAL)

    PUT #FILENUM, START, REC
END SUB

SUB GETTRANSACTION(FILENUM AS INTEGER, START AS LONG, T AS TRANSACTION)
    DIM REC AS STRING*51

    REC = SPACE$(51)
    GET #FILENUM, START, REC
   
    T.TRANSEQ = CVI(MID$(REC,  1, 2))
    T.TRANDATE =    MID$(REC,  3, 8)
    T.PAYEE =       MID$(REC, 11, 20)
    T.CHECK =       MID$(REC, 31, 4)
    T.TRANTYPE =    MID$(REC, 35, 1)
    T.AMOUNT = CVD( MID$(REC, 36, 8))
    T.RUNBAL = CVD( MID$(REC, 44, 8))

END SUB

OPEN "FILE.DAT" FOR RANDOM AS #1 LEN=51
GETTRANSACTION 1, 1, EXAMPLE
CLOSE #1

OPEN "FILE.DAT" FOR RANDOM AS #1 LEN=51
PUTTRANSACTION 1, 1, EXAMPLE
CLOSE #1
You may notice the use of MKI$/CVI and MKD$/CVD to pack/unpack INTEGER and DOUBLE variables. Functions like this convert numerical data to/from a string representation containing the bytes of the data as they would be found in memory.
Edit: updated to avoid Getting/Putting a var-len String, since it will have a two-byte header prepended in QB.
petan
Posts: 683
Joined: Feb 16, 2010 15:34
Location: Europe
Contact:

Re: Simple file I/O issue

Postby petan » May 20, 2013 5:53

Welcome , skystrick. To be familiar with random files take alook on this:
http://www.freebasic.net/forum/viewtopic.php?f=3&t=20150
http://www.freebasic.net/forum/viewtopic.php?f=2&t=19931
http://www.petesqbsite.com/forum/viewtopic.php?t=3354 and others examples for random files on this site.

and use hexa printout of your file contents (via notepad) to catch where is difference in field element's length.

Pete
Merick
Posts: 1038
Joined: May 28, 2007 1:52

Re: Simple file I/O issue

Postby Merick » May 20, 2013 7:07

simplest way:

Code: Select all

TYPE TRANSACTION
   TRANSEQ AS INTEGER
   TRANDATE AS STRING * 8
   PAYEE AS STRING * 20
   CHECK AS STRING * 4
   TRANTYPE AS STRING * 1
   AMOUNT AS DOUBLE
   RUNBAL AS DOUBLE
END TYPE

DIM AS TRANSACTION EXAMPLE, example2

with example
   .transeq = 1
   .trandate = "May 20th"
   .payee = "John Doe"
   .check = "3184"
   .trantype = "S"
   .amount = 24.98
   .runbal = 120.34
end with

dim as integer f
f = freefile

open "test.dat" for random as #f
put #f,, example
close #f

open "test.dat" for random as #f
get #f,, example2
close #f
with example2
print .transeq
print .trandate
print .payee
print .check
print .trantype
print .amount
print .runbal
end with

sleep


As you can see with this, if you omit the second parameter of put - "put #f,, example" - then the function will store the whole data structure of the user defined type to the file, and get will do the same to automatically sort the data into the correct slots. This will not work with pointers though - if the UDT has a pointer as a member, it will only store the memory address of the pointer and not the data stored at that address.
fxm
Posts: 9990
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Simple file I/O issue

Postby fxm » May 20, 2013 8:11

Merick wrote:simplest way:

But if you compile with error checking (fbc -exx), this no more works and you get a file I/O error on 'put #f,, example'.

As said counting_pine, you must provide 128 bytes per default.
One solution is to use this UDT:

Code: Select all

TYPE TRANSACTION FIELD = 1
   TRANSEQ AS INTEGER
   TRANDATE AS STRING * 8
   PAYEE AS STRING * 20
   CHECK AS STRING * 4
   TRANTYPE AS STRING * 1
   AMOUNT AS DOUBLE
   RUNBAL AS DOUBLE
   dummy(1 to 71) as byte
END TYPE

Or automatic padding computation:

Code: Select all

TYPE TRANSACTION FIELD = 1
   TRANSEQ AS INTEGER
   TRANDATE AS STRING * 8
   PAYEE AS STRING * 20
   CHECK AS STRING * 4
   TRANTYPE AS STRING * 1
   AMOUNT AS DOUBLE
   RUNBAL AS DOUBLE
END TYPE

Type TRANSACTIONwithPADDING Extends TRANSACTION FIELD = 1
   dummy(1 to 128-sizeof(TRANSACTION)) as byte
END Type

DIM AS TRANSACTIONwithPADDING EXAMPLE, example2

with example
   .transeq = 1
   .trandate = "May 20th"
   .payee = "John Doe"
   .check = "3184"
   .trantype = "S"
   .amount = 24.98
   .runbal = 120.34
end with

dim as integer f
f = freefile

open "test.dat" for random as #f
put #f,, example
close #f

open "test.dat" for random as #f
get #f,, example2
close #f
with example2
print .transeq
print .trandate
print .payee
print .check
print .trantype
print .amount
print .runbal
end with

sleep
Remark: the two 'FIELD = 1' may be now optional.
petan
Posts: 683
Joined: Feb 16, 2010 15:34
Location: Europe
Contact:

Re: Simple file I/O issue

Postby petan » May 20, 2013 11:34

From PowerBasic I am drilled to everytime using of 'LEN' for RANDOM file access! You MUST to know length of record.
So working code is ( compiler linux option fbc -exx -w pedantic "%f" )

Code: Select all

screen 18

TYPE TRANSACTION
   TRANSEQ AS INTEGER
   TRANDATE AS STRING * 8
   PAYEE AS STRING * 20
   CHECK AS STRING * 4
   TRANTYPE AS STRING * 1
   AMOUNT AS DOUBLE
   RUNBAL AS DOUBLE
END TYPE

DIM AS TRANSACTION example, example2

with example
   .transeq = 1
   .trandate = "May 20th"
   .payee = "John Doe"
   .check = "3184"
   .trantype = "S"
   .amount = 24.98
   .runbal = 120.34
end with

dim as integer f
f = freefile

'open "test.dat" for random as #f
open "test.dat" for random as #f LEN=Len(example)
put #f,, example
close #f

'open "test.dat" for random as #f
open "test.dat" for random as #f LEN=Len(example2)
get #f,, example2
close #f
with example2
print .transeq
print .trandate
print .payee
print .check
print .trantype
print .amount
print .runbal
end with

sleep
end

Pete
Last edited by petan on May 20, 2013 19:43, edited 1 time in total.
skystrick
Posts: 104
Joined: May 19, 2013 23:25
Location: Florida

Re: Simple file I/O issue

Postby skystrick » May 20, 2013 13:02

fxm wrote:One solution is to use this UDT:

Code: Select all

TYPE TRANSACTION FIELD = 1
   TRANSEQ AS INTEGER
   TRANDATE AS STRING * 8
   PAYEE AS STRING * 20
   CHECK AS STRING * 4
   TRANTYPE AS STRING * 1
   AMOUNT AS DOUBLE
   RUNBAL AS DOUBLE
   dummy(1 to 71) as byte
END TYPE


Okay, this seems the simplest of everything here. Make every record be 128 bytes by padding it out with blank space. Three questions:

(1) Is there not a way, perhaps using a LEN argument, to redefine the length of the the records in a file from the default of 128? If I know that I have a file which exclusively contains these TYPE TRANSACTION records, each of which is 57 characters long, can't I use a LEN arguement with my GET and PUT statements so that I don't have this issue, and don't artifically increase the size of data stored on limited-size disks (floppies) by padding it out with useless filler data?

(2) Is there a plan to fix this in future editions of FreeBASIC, so that it's more in-line with the simplicity of GET and PUT statements under QB?

(3) Okay, so I see how to solve the issue with files where each record is of TYPE TRANSACTION, because they're of predictable length (same every time). But let's say I'm working with a file which doesn't use the TYPE TRANSACTION, and contains variables of different types and lengths. As in my original example:

LET A = 1
LET B$ = "HELLO"
LET C$ = "TESTING"
OPEN "FILE.DAT" FOR RANDOM AS #1
PUT #1, 1, A
PUT #1, 2, B$
PUT #1, 3, C$
CLOSE #1


Should create a three-line file which looks like this:

1
HELLO
TESTING

However, it does not. When I run a program to input the data from the saved file:

OPEN "FILE.DAT" FOR RANDOM AS #1
GET #1, 1, A
GET #1, 2, B$
GET #1, 3, C$
PRINT A
PRINT B
PRINT C
CLOSE #1

That program should print, on my screen:

1
HELLO
TESTING

But it does not.

It manages to input the integer variable (A) from of line #1, but it cannot pull the string variables (B$ and C$) out of lines 2 and 3.

So my screen looks like this:

1


And that's it.
fxm
Posts: 9990
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Simple file I/O issue

Postby fxm » May 20, 2013 14:13

See the previous post of petan.

Read at documentation /wiki/KeyPgType
and specially the paragraph 'Dynamic data'

It is why there is more simple to work with each fix-len string length in UDT that overvalues all the useful string sizes, and also a fix record lenght for file that also overvalues all the useful UDT sizes.
So, we can always read/write the Nth record with 'Get/Put #file_number, N, UDT_instance'.
counting_pine
Site Admin
Posts: 6229
Joined: Jul 05, 2005 17:32
Location: Manchester, Lancs

Re: Simple file I/O issue

Postby counting_pine » May 20, 2013 14:50

I've posted a bug report about FB's Random incompatibilities at #673. I don't see why they shouldn't be fixable.
Unfortunately, an additional incompatibility with QB lies with using String*n in a structure, because it uses a different size internally. This means the naive method of reading/writing will not produce files that are compatible with QB. It's why for the moment I advise packing the data into a fixed-length String.

Return to “General”

Who is online

Users browsing this forum: No registered users and 8 guests