---------------------------------------------------------
The Liberty Basic Newsletter - Issue #35 - MAY 99
"Knowledge is a gift we receive from others."
		- Michael T. Rankin
---------------------------------------------------------
In This Issue:

Part Three of a multi-part series on Disk File
Functions by Dean Hodgson.  Brosco and Alyce wish again
to thank Dean for this OUTSTANDING series!  

If you also learned from this article, why not comment 
here or write directly to Dean:  
mailto:dhodgson@nexus.edu.au
------------
In Future Issues:

- Parts 4 and 5 of Dean's series on Disk File Functions.

- Theory of Debugging.

- Handy tips.

- Programmer's Spotlight

- More great articles by guest authors!
---------------------------------------------------------
DISK FILE HANDLING IN LIBERTY BASIC
By Dean Hodgson
copyright (c) 1999
dhodgson@nexus.edu.au

Part 3 - Windows API disk file commands
---------------------------------------

Windows has its own set of API disk file handling functions. 
They work very differently to LB's own field commands and 
they allow you to do some things that LB itself does not 
permit such as sharing networked files and reading or 
writing data to specific parts of a file.

First, you need to open the API Dynamic Link Libraries (DLL):
    OPEN "kernel" for DLL AS #kernel
    OPEN "user" for DLL AS #user

Remember to close these at the end of your program with
    CLOSE #kernel
    CLOSE #user


GetDriveType
------------
This API function can tell you the type of drive that is available. 

    CheckDrive = 1
    CALLDLL #kernel, "GetDriveType", _
       CheckDrive AS short,_
       result AS word

CheckDrive is a number indicating the drive where 0 is for 
drive A, 1 is for drive B, 2 for C, etc. The result is a 
number that indicates the type of drive:

  2  floppy disk
  3  hard disk
  4  network drive

You don't have to insert a disk for this function to work. 
A Cd-Rom drive often returns a value of 4.

SetErrorMode
------------
You call this API function before doing any of the others 
below. And you must call it again afterwards. This functions 
turns on/off Windows own file error messages.
    ErrorMode = 1
    CALLDLL #kernel, "SetErrorMode", _
       ErrorMode AS word,_
       result AS word

Setting ErrorMode to 1 turns off Windows error messages. What 
then happens is the API calls below all return an error value. 
Setting ErrorMode = 0 turns Windows error messages back on. The 
result can generally be ignored.

As an example, after doing the above, you can try to create 
a file on drive A. Do not put a floppy disk in when doing this 
and you'll see the error. The program is:

    OPEN "kernel" for DLL AS #kernel      'open the API libraries

    ErrorMode = 1                 'turn off Windows error trapping
    CALLDLL #kernel, "SetErrorMode",_
       ErrorMode AS word,_
       result AS word

    File$ = "A:\TEST"             'going to try to create a file
    Attrib = 0
    CALLDLL #kernel, "_lcreat",_
       File$ AS ptr,_
       Attrib AS short,_
       result AS short

    IF result = -1 THEN
        NOTICE "An error happened accessing DRIVE A:"
    ELSE
        KILL File$
    END IF

    ErrorMode = 0                  'turn error trapping back on
    CALLDLL #kernel, "SetErrorMode",_
        ErrorMode AS word,_
        Result AS word

    CLOSE #kernel


FILE EXISTS TEST
----------------
The API call _lopen can be used to see if a file exists. 
This function is described below.
    File$ = "TEST"      'filename we are checking for
    Type = 16384        'use this value to test for a file
        CALLDLL #kernel, "_lopen",_   'try to open the file
           TestFile$ AS ptr,_
           Type AS short,_
           result AS short

IF result<>-1 THEN NOTICE "File Exists" ELSE NOTICE "File Doesn't Exist"

The result should be 0 if the file exists.

CREATING A FILE _lcreate
------------------------
Unlike LB's commands, the API makes a difference between 
creating files and opening already existing files. This means 
you must check for the existence of a file before opening it, 
if you do not know it is present.

    Attrib=0
    CALLDLL #kernel, "_lcreate",_
       File$ AS ptr,_
       Attrib AS short, _
       result AS short

Attrib is a value indicating the type of file:
  0 normal read/write file
  1 read only file
  2 hidden file
  3 system file

The result is a number assigned by Windows to the open file. 
If this value is -1, an error has happened.

Note: If you attempt to create an existing file, all the 
contents of that file are lost!!


OPENING A FILE _lopen
---------------------
The API does not differentiate between Sequential, Random and 
Binary files. All files are essentially Binary when opened but 
they have certain read/write properties. _lopen is used to 
open a file that has been created. If you try to open a file 
that does not exist, the result will be an error.

    FileType=2
    CALLDLL #kernel, "_lopen",_
       File$ AS ptr,_
       Type AS short,_
       FileHandle AS short
    NOTICE "File handle for "+TestFile$+" is "+STR$(FileHandle)

The result of _lopen is a number called the "File handle". 
Windows assigns this value to an open file, unlike LB's 
commands where you assign the handle. You then use the 
FileHandle value in all subsequent operations on the open 
file. The value is different each time you open the 
If there was an error opening, FileHandle is -1.

The FileType is important and offers functionality not found 
in LB's OPEN statement. Here are some of the values you can 
use when opening files.

 0 is read only        users can only perform read operations on the file
 1 is write only       users can only write to the file
 2 is read/write       users can either read or write to the file
 64 is shared read only   allows network shared access for reading only
 65 is shared write only  allows network shared access for writing only
 66 is shared read/write  allows network shared access for reading and writing

Under normal circumstances, you should set the FileType to 2 
for use on stand-alone computers or 66 for use in a network 
situation where more than one user can access the file at the 
same time. If you don't know the situation in which your 
software will be used, 66 is a reasonable default.

If a file has been opened using a 0 and another computer on a 
network tries to open the same file, they will get an error. 
If the file is opened using 64 and another user tries to open 
with a 64, no error will occur and both machines will be able 
to read from the file at the same time. However, if another 
user tries to open the same file with a different value, they 
will get an error.

WRITING TO A FILE _lwrite
-------------------------
The function _lwrite can be used to write strings up to 64k 
long to a file.

    S$="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"
    LengthOfString=LEN(S$)
    CALLDLL #kernel, "_lwrite",_
        FileHandle AS short,_
        S$ AS ptr,_
        LengthOfString AS short,_
        result AS short

The exact string will be written to the file as is. No 
end-of-line markers (CHR$(13) CHR$(10)) are written. If you 
want these, you need to add them to the string yourself 
before writing and include them in the length.

FileHandle is the value returned by _lopen.

LengthOfString is the number of bytes to write. If this value 
is less than the actual length of the string, then only that 
number of characters are written. If the value is greater then 
an error occurs.


SEEK _llseek
-----------
The API contains a function for positioning the 'file pointer' 
to any spot within a file. This pointer specifies where you 
will start reading or writing data. Note: the pointer is 
automatically moved to the end of any data that is read or 
write by _lread or _lwrite. _llseek is used to move the 
pointer to another position within the file.

    Position=5
    Option=0
        CALLDLL #kernel, "_llseek",_
            FileHandle AS short,_
            Position AS long,_
            Option AS short,_
            result AS long

Position is the number of bytes to move the pointer. If 
Option=0, then Position is the number of bytes from the 
start of the file, with the first byte being 0. If 
Option=2, then Position is the number of bytes from the 
end of the file. Option=1 tells the pointer to move the 
specified number of bytes from the current position. If 
the number is positive, the move is toward the end of 
the file. If the number is negative, the move is toward 
the beginning. If you try to position outside the file, 
a -1 error will result. Otherwise the result will be the 
current pointer position in bytes from the start of the file.

For random access files, you can position the pointer to 
the beginning of a record by multiplying the record length 
by the record number -- 425 * 6 puts the pointer at the 
start of record 6. Note: that record numbers start with 
0 and not 1!!

READING FROM A FILE _lread
--------------------------
This is similar to _lwrite. However, you need to set up a 
buffer string in order to receive the data. In the example 
below, we are going to read 10 bytes from the file. Therefore 
the buffer needs to be at least 10 bytes long. We also need 
to add a CHR$(0) at the end to tell LB to deal with this 
string properly within the DLL call.

    Buffer$ = SPACE$(10) + CHR$(0)
    Readbytes = 10
    CALLDLL #kernel, "_lread",_
        FileHandle AS short,_
        Buffer$ AS ptr,_
        Readbytes AS short,_
        result AS short

The value in Readbytes should not be greater than the length 
of Buffer$! If an error happens, the result will be -1, 
otherwise it will be the number of bytes read.

CLOSING THE FILE _lclose
------------------------
    CALLDLL #kernel, "_lclose",_
        FileHandle AS short,_
        result AS short

The result will be 0 if the file was closed properly.

CLEANING UP
-----------
Remember to turn error message reporting back on.
   wMode = 0
   CALLDLL #kernel, "SetErrorMode", _
       wMode AS word,_
       result AS word

And, of course, to close the API DLL files
    CLOSE #kernel
    CLOSE #user

--------------------------------------------------------------------

An example:

Here is a random access file example using just the API disk 
calls. It does the same thing as the example given for LB 
in the previous part. In order to make the functions 
"general purpose" and reusable, I've put them into 
subroutines. Also, those readers clever enough may be able 
to see how to use structs to hold record data instead of 
the method I've employed.

OPEN "kernel" for DLL AS #kernel
OPEN "user" for DLL AS #user

GOSUB [ErrorOff]                               'turn off Windows errors
File$="TEMP.DAT"                               'The filename
GOSUB [CreateFile]                             'Create file (it'll be open)
GOSUB [CloseFile]                              'Close it

GOSUB [OpenFile]                               'Open the file
A$="123" : NBW=10 : lwrite$=A$ : GOSUB [Write] 'first field, first record
B$="456" : NBW=5 : lwrite$=B$ : GOSUB [Write]  'second field, first record
A$="789" : NBW=10 : lwrite$=A$ : GOSUB [Write] 'first field, second record
B$="ABC" : NBW=5 : lwrite$=B$ : GOSUB [Write]  'second field, second record
NBW=15 : lwrite$="" : GOSUB [Write]            'blank record at end
GOSUB [CloseFile]

recsize=15
File$="TEMP.DAT" : GOSUB [OpenFile]            'open file
print "Record one"
seek=0 : GOSUB [Seek]                          'put pointer at record 0
NBR=10 : GOSUB [Read] : A$=TRIM$(lread$)       'read first field
NBR=5 : GOSUB [Read] : B$=TRIM$(lread$)        'read second field
PRINT A$
PRINT B$
PRINT "Record two"
seek=1 * recsize : GOSUB [Seek]                'pointer to 2nd record (1)
NBR=10 : GOSUB [Read] : A$=TRIM$(lread$)       'read first field
NBR=5 : GOSUB [Read] : B$=TRIM$(lread$)        'read second field
PRINT A$                                       'show fields
PRINT B$
GOSUB [CloseFile]

GOSUB [ErrorOn]                          'clean up
CLOSE #kernel
CLOSE #user
END

'Opens a file in shared read/write mode
'Pass File$ as the filename
'Returns FileHandle. 0 if an error.
[OpenFile]
    FileNumbere=1
    CALLDLL #dl,"DOPENR",_
        FileNumber AS short,_
        File$ AS ptr,_
        recsize AS short,_
        result AS short
    RETURN

'Read string from file
'NBR is number of bytes to read
'Result is returned in string lread$, which does not have spaces 
'truncated.
'Result is -1 if an error
[Read]
    lread$=""
    temp$=SPACE$(NBR)+CHR$(0)         'temporary string to receive data
    CALLDLL #kernel, "_lread", FileHandle AS short, temp$ AS ptr, NBR AS short, result AS short
    IF result>=1 THEN lread$=LEFT$(temp$,result) 'strip 0 at end of string
    RETURN

'Writes the string lwrite$
'NBW is the number of bytes to write
[Write]
    temp$=lwrite$                       'make it a temporary string
    temp=NBW-LEN(temp$)                     'test to see if string isn't right length
    IF temp>0 THEN temp$=temp$+SPACE$(temp)   'too short, add spaces
    IF temp<0 THEN temp$=LEFT$(temp$,NBW)         'too long, truncate
    CALLDLL #kernel,"_lwrite",FileHandle AS short,temp$ AS ptr,NBW AS short,result AS short
    RETURN

'seek file pointer, the number of bytes from the start of the file
[Seek]
    temp=0
    CALLDLL #kernel, "_llseek", FileHandle AS short, seek AS long, temp AS short, result AS long
    RETURN

[CloseFile]
    CALLDLL #kernel, "_lclose", FileHandle AS short, result AS short
    RETURN

'==================================================================

--------------------------------------------------------------------

API DISK FILE LIMITATIONS
-------------------------
* You can only read and write data via strings. You must 
  convert numeric data using VAL and STR$ functions.

* There are no special commands and functions for dealing 
  with sequential and random access files. A file is 
  essentially a block of bytes.

* The structure of a random access file is up to you. 
  To read an entire record, you would read the whole 
  record into a string then break it up into your 
  fields using MID$.
=====================================================================

---------------------------------------------------------
 Newsletter compiled and edited by: Brosco and Alyce.
 Comments, requests or corrections: Hit 'REPLY' now!
---------------------------------------------------------