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

Parts 4 and 5 of a multi-part series on Disk File
Functions by Dean Hodgson.  Thanks,  Dean for this 
OUTSTANDING series!  

Part 4 - Deanslib disk file commands
Part 5 - Sharing files on networks

Deanslib.dll is attached for your convenience.
---------------------------------------------------------
DISK FILE HANDLING IN LIBERTY BASIC
By Dean Hodgson
copyright (c) 1999
dhodgson@nexus.edu.au

Part 4 - Deanslib disk file commands

Deanslib is a freeware DLL file containing over 100 functions 
that can be called from Liberty Basic. A range of disk file 
functions is included. The random access numeric functions 
read and write integer numeric data types in IEEE format. 
Also, the functions are designed for use in a network 
situation where data needs to be shared.

Deanslib supports Sequential and Random Access file types.

To initialize the DLL use the command 
OPEN "DEANSLIB.DLL" FOR DLL AS #dl

SEQUENTIAL FILES
================

Opening a file for reading DOPENI
---------------------------------
This function opens a sequential file for reading data. The sytax is:
  CALLDLL#dl,"DOPENI",_
    filenumber AS short,_
    filename$ AS ptr,_
    result AS short

The filenumber is a unique number that you provide, 
from 1 to 99. You use this same number for all other calls 
to the functions below.

The filename is the name of your file. It can include the path.
The result indicates if there was an error when the file was 
opened. It is 0 if there was no error or not 0 if there was.
The file is opened for shared multi-user read-only access so 
can be used in network situations.

Opening a file for writing DOPENO
---------------------------------
DOPENO is used when you want to write to a sequential file.
  CALLDLL#dl,"DOPENO",_
    filenumber AS short,_
    filename$ as ptr,_
    result AS short

The parameters here are the same as for DOPENI.

DPOPENA opens a file for appending extra data at the end:
  CALLDLL#dl,"DOPENA",_
    filenumber AS short,_
    filename$ AS ptr,_
    result AS short

Closing a File
--------------
  CALLDLL#dl,"DCLOSE",_
    filenumber AS short,_
    result As short


Reading sequential data
-----------------------
There are two functions that can be used to read sequential data. 
Both deal with strings only.

buffer$=SPACE$(length)+CHR$(0)
CALLDLL#dl,"DREADS",_
    filenumber AS short,_
    buffer$ AS ptr,_
    length AS short,_
    result AS short

DREADS reads data into a string and therefore works similarly 
to LBs INPUT$ function. The variable buffer$ is a return 
string and must contain as many blank spaces as the number 
of bytes to be read (length) allocated before calling the 
function, and it must end in a CHR$(0). This is the way LB 
passes data from DLL functions to strings.

The length is the number of bytes to be read.
The result is a 0 if there was no error or not 0 if there was.

buffer$=SPACE$(length)+CHR$(0)
CALLDLL#dl,"DINPUT",_
    filenumber AS short,_
    buffer$ as prt,_
    result AS short
This function works like LB's LINE INPUT#. It reads a string 
until a CHR$(13) end-of-line marker is encountered.

If you are inputting numeric data, read it using these 
functions as a string then change to a number via LBs 
VAL function. For example:

length=10
buffer$=SPACE$(length)+CHR$(0)
CALLDLL#dl,"DINPUT",filenumber AS short, buffer$ AS ptr, result AS short
number=VAL(buffer$)

Writing sequential data
-----------------------
Only two commands are available. The first writes a single 
string and adds and end-of-line marker automatically. The 
result is 0 for no error.

CALLDLL#dl,"DPRINT",_
    filenumber AS short,_
    string$ AS ptr,_
    result AS short

The second is similar to the _lwrite API function and writes 
the specified number of bytes from a string.

CALLDLL#dl,"DWRITES",_
    filenumber AS short,_
    string$ AS ptr,_
    length AS short,_
    result AS short


RANDOM ACCESS FILES
===================
Deanslib contains a range of functions for dealing with 
random access files. To open a random file, use the function

  CALLDLL#dl,"DOPENR",_
    filenumber AS short,_
    filename$ AS ptr,_
    recordlength AS short,_
    result AS short

The variable recordlength is the number of bytes in each record. 
The result is 0 if the file is successfully opened or created. 
This function opens files in shared read/write mode which allows 
multiple access of the same file on networks.

Use DCLOSE to close the file.

Positioning the File Pointer
----------------------------
This function is similar to the API function _llseek.
  CALLDLL#dl,"DSEEK",_
    filenumber AS short,_
    position AS long,_
    result AS short

Position is the number of bytes from the start of the file. 
If Position is a negative number, it represents the number 
of bytes from the end of the file. A result other than 0 
indicates an error, such as trying to position past the end 
of the file. To position to a particular record use 
recordlength * recordnumber to get the number of bytes.

Reading Random Access Data
--------------------------
Functions are available for reading and writing numbers in 
IEEE format and for strings.

This function reads a 2 byte signed short integer into result.
Filenumber is the number you assigned in DOPENI.
  CALLDLL#dl,"DREADI", filenumber AS short, result AS short

Reads a 4 byte signed long integer value.
  CALLDLL#dl,"DREADL", filenumber AS short, result AS long

DREADS reads string data
  buffer$=SPACE$(length)+CHR$(0)
  CALLDLL#dl,"DREADS",_
      filenumber AS short,_
      buffer$ AS ptr,_
      length AS short,_
      result AS short
buffer$ is the string that receives the data.
length is the number of bytes. This cannot exceed 32767 bytes.
result is 0 for a successful read or not zero for an error.

Writing Random Access Data
--------------------------
2 byte short integers are written using
  CALLDLL#dl,"DWRITEI",filenumber AS short, value AS short, result AS short

4 byte long integers are written with
  CALLDLL#dl,"DWRITEL",filenumber AS short, value AS long, result AS short

Strings are written with DWRITES described previously.

Extra Things
============

DSHARE
This indicates whether or not SHARE is active. It is 
normally available if you are using Window 3.11, 95, 98 
or NT but may not be present under 3.1.

The open functions require SHARE to be present.
  CALLDLL#dl,"DSHARE", result AS short
A 0 is returned if not present or -1 if present.

DFLUSH
Data can remain within the computer's internal buffers. 
This function forces data to be written, which can be 
useful before closing a file.
  CALLDLL#dl,"DFLUSH", filenumber AS short, result AS void

FILEHANDLE
You can obtain the Windows filehandle value for the open 
file using this function. Once you have the handle, you 
can then use the API disk functions _llseek, _lread and 
_lwrite as well as the Deanslib functions.
  CALLDLL#dl,"FILEHANDLE",filenumber AS short, result AS short

There are also functions dealing with record locking, 
which is part of networking. This is covered in the final section.

EXAMPLE
=======
Here is the same random access program as written for the 
API calls but using Deanslib functions instead.

OPEN "DEANSLIB.DLL" for DLL AS #dl
File$="TEMP.DAT"                               'The filename
recsize=15                                     'Set up our record size
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]                              'close file

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]

CLOSE #dl
END

'Opens a file in shared read/write mode
'Pass File$ as the filename, recsize as the record length and FileNumber
[OpenFile]
    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.
[Read]
    lread$=""
    temp$=SPACE$(NBR)+CHR$(0)         'temporary string to receive data
    CALLDLL #dl, "DREADS",_
        FileNumber 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 #dl,"DWRITES",_
        FileNumber 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]
    CALLDLL #dl, "DSEEK", FileNumber AS short, seek AS long, result AS long
    RETURN

[CloseFile]
    CALLDLL #dl, "DCLOSE", FileNumber AS short, result AS short
    RETURN


=====================================================================

Part 5 - Sharing files on networks

Networks allow many users to access programs and files stored 
on a central computer. There are various networking 
configurations but to simplify this discussion we'll 
assume one computer is a "server" where common programs 
and files are stored and the other computers are 
"workstations". Visualize the situation where the program 
you are using is stored on each workstation's hard drive 
but common data files are stored on the Server and all the 
workstations can access those files.

It is possible that two or more workstations ("users") may 
try to access the same file at the same time. User A might 
open the file and read or write to it, and while that is 
happening User B might also try to open the file and do 
something. Dangers lurk here!

1. What happens if User A does not want anyone else to 
change the file while they have it open?

2. What happens when User B tries to write to part or 
all of the file while User A is also trying to write to it?

Situation 2 is dangerous and if unprotected can lead to 
severe data corruption. Networks do not automatically 
protect against this possibility. Your program must be 
able to protect the data you're working with and it must 
know if that data is protected or "locked".

There are two methods of protection:  file locking and 
record locking.

File locking involves protecting the entire file so only 
one user can work on it at a time. While the file is locked, 
no other user can use the file. File locking is established 
when the file is opened. Files can be opened for single-user 
restricted access, for multi-user read (many users can read 
the file at the same time) but single-user write, multi-user 
write and single-user read (this isn't sensible actually) and 
multi-user read/write.

If you are using large random access files, it is possible 
to permit multi user reading and writing except at certain 
records that are being used at the moment. These records can 
be locked as well.

Programs such as Microfsoft Word lock a whole document file 
when you open it. If anyone else on the network opens that 
same file while locked, they receive an error and the file 
is reported as being in use. Dos would report a 
"sharing violation" error. Liberty Basic opens files in 
unshared modes so they are essentially locked as well and 
produce sharing error messages. There is no easy method in 
LB to detect if a file is locked. However, this can be done 
using the API calls and Deanslib.

File Locking using API Functions
================================
The attribute you assign to a file when it is opened determines 
whether or not it can be shared.
     0 is read only        single-user read only      _OF_READ
     1 is write only       single-user write only     _OF_WRITE
     2 is read/write       single-user read or write  _OF_READWRITE
     64 is shared read only   multi-user read only
     65 is shared write only  multi-user write only
     66 is shared read/write  multi-user read/write

  CALLDLL #kernel, "_lopen", File$ AS ptr, Type AS short, Handle AS short

The last three use _OF_SHARE_DENY_NONE ORd with one of the 
first three.

If a file is opened by user A using an attribute of 2 and 
user B tries to open that file, _lopen returns to user B an 
error and the file is not opened. Therefore, it is necessary 
to trap for errors when opening files. The FileHandle returned 
will have a -1 error. What should then happen is that User B's 
computer should either produce a message indicating that the 
file is in use or pause and try opening the file again until 
successful (User A has finished) or a specified number of 
attempts have been made and all have failed. The latter is 
essential to avoid "deadly embrace" situations where user 
A's computer has crashed leaving the file open.

For random access files, it is usually safe to allow 
multi-user read access (attribute value 64) but it is 
necessary to restrict write access to one user (values 1 or 2). 
Any number of users can open and read the file, but if someone 
wants to write to it, they'll get an error when trying to 
open until everyone reading has closed the file. If the file 
is open for writing by one user, everyone else will get an 
error until that file is closed.

This approach is reasonable where files are not left open for 
long periods of time. Its major drawback is where a computer 
has crashed, leaving a file open in the process.

Here is a simple example problem that will lock a file. To see 
it working, you will need to test it on a network by running 
the program on two computers at the same time, changing File$ 
to include the path to the shared file. When run on the first 
computer, the message "File opened successfully" should appear. 
Don't press enter (this keeps the program running and the file 
open). Run the program on the second computer. The message 
"File open error" should appear on the second computer.

OPEN "KERNEL" FOR DLL AS #kernel
temp=1
CALLDLL#kernel,"SetErrorMode",temp AS short,result AS void
File$="TEMP.DAT"
Filetype=_OF_READWRITE
CALLDLL#kernel,"_lopen",File$ AS ptr,Filetype AS word, FileHandle AS word
IF FileHandle<0 OR FileHandle>32767 THEN 
PRINT "File open error" 
ELSE PRINT "File opened successfully"
END IF
PRINT "Press enter to close file"
INPUT A$
CALLDLL#kernel,"_lclose",FileHandle AS word,result AS void
temp=0
CALLDLL#kernel,"SetErrorMode",temp AS short,result AS void
CLOSE #kernel
PRINT "Close Window"
END

To make the file sharable change the variable Filetype to  
Filetype=_OF_READWRITE OR _OF_SHARE_DENY_NONE. The second 
part indicates the file should be shared and not deny access 
to anyone. Other options include_OF_READ and _OF_WRITE and 
these are always ORd with the sharing constant.

RECORD LOCKING
==============
Record locking is performed in random access files. Individual 
records are locked rather than the whole file. This makes most 
of the file accessable to users except for any records that are 
being modified. However, trapping and dealing with record locks 
require more work than file locking.

There are no Win 3.11 API calls to lock records. The only way to 
lock records when using the API disk functions is to use the 
Deanslib LOCK and UNLOCK functions, described below. These 
functions contain bits of machine code that perform locking 
via behind-the-scenes Dos calls.

DLOCK
This function locks a range of bytes in a file opened with DOPENR.
  CALLDLL#dl,"DLOCK",_
    filenumber AS short,_
    position AS long,_
    count AS long,_
    result AS short
Position is the number of bytes from the start of the file 
where you want to start locking. Byte 0 is the first byte. 
For records it is recnumber * reclength.

Count is the number of bytes you want to lock. For most files, 
this is the length of the record, although it could span any 
number of records.

Result = 0 if the lock was successful, indicating that no 
other system had locked the selected range. Any other value 
is an error, probably indicating that some or all of the 
bytes are already locked.

DUNLOCK
This unlocks locked bytes. You must call this to release the 
lock section.
  CALLDLL#dl,"DUNLOCK",_
    filenumber AS short,_
    position AS long,_
    count AS long,_
    result AS short
The parameters are identical to DLOCK. Make sure that position 
and count are the same as in the locking call.

DLOCKWAIT
This works like DLOCK but if an error occurs when the lock is 
attempted -- usually indicating that the bytes are already 
locked -- the lock attempt is repeated every 3 seconds 
until a maximum of 25 tries has been made. DLOCK does not 
wait but returns an error if a region is already locked. 
Use DUNLOCK to unlock.
  CALLDLL#dl,"DLOCKWAIT",_
    filenumber AS short,_
    position AS long,_
    count AS long,_
    result AS short

LOCK
This function locks a region of bytes but you supply the file 
handle value rather than the filenumber. LOCK is intended to be 
used when you are using API functions.
  CALLDLL#dl,"LOCK",_
    FileHandle AS short,_
    position AS long,_
    count AS long,_
    result AS short
  
UNLOCK
This unlocks the bytes locked by LOCK. Again, the file handle is used.
  CALLDLL#dl,"UNLOCK",_
    FileHandle AS short,_
    position AS long,_
    count AS long,_
    result AS short


Below is a simple example of record locking to show that it 
works. The file TEMP.DAT is opened using DOPENR which does 
not restrict access. The function DLOCK is used to lock the 
first record of 10 bytes. If the result is 0, the record is 
unlocked else it is assumed to be locked. Run this program 
on the first computer but don't press enter, then run it on 
the second computer which should indicate that the record is 
locked.

OPEN "DEANSLIB.DLL" FOR DLL AS #dl
File$="TEMP.DAT"
Filenumber=1
recsize=10
CALLDLL#dl,"DOPENR",Filenumber AS short, File$ AS ptr, recsize AS word, result AS word
position=0
count=10
CALLDLL#dl,"DLOCK",Filenumber AS short,position AS long,count AS long,result AS short
IF result=0 THEN
  PRINT "Record locked"
  PRINT "Press enter to unlock and close file"
  INPUT A$
  CALLDLL#dl,"DUNLOCK",Filenumber AS short,position AS long,count AS long,result AS short
  PRINT "Record unlocked"
ELSE
  PRINT "Record locked"
END IF
CALLDLL#dl,"DCLOSE",Filenumber AS word,result AS short
CLOSE #dl
PRINT "Close Window"
END


For the technically minded, here are the machine code calls 
needed to lock and unlock records. The starting position 
is a 4-byte 32-bit long integer. The CX register contains 
the high word of the starting position, and DX contains 
the low word. The length is also a long int with the high 
word in SI and the low word in DI. The AH register contains 
$5Ch and AL contains a 0 to indicate a lock or a 1 to unlock. 
BX contains the Dos file handle. The call is Interrupt $21h 
service $5Ch. If successful the CX register contains 0, 
unsuccessful CX contains 1 and the AX register is the error 
code. Please note that Dos does not automatically unlock a 
file region when a program terminates or a file is closed!


SEMAPHORE LOCKING
=================
Semaphore locking is a trick commonly used in industrial 
software. A semaphore is a flag that is raised when an event 
occurs. The actual data files and records being worked on are 
not locked. Instead a special file is locked and set up to 
contain information as to who is locking what. This is the 
flag, a notice to everyone that something important is 
happening, so hands off! Semaphore locking is also known 
as a "soft lock" as opposed to a "hard lock" described 
above and can overcome some of the problems of hard locks.

There are two methods. In one, a special file is opened and 
locked before dealinig with any other file. All file 
operations of the program check the status of this file 
before accessing any other files. If the semaphore file is 
already locked, the program waits and tries again, repeating 
until the lock has been cleared by the locking computer or 
until a specified time out.

Another variant of soft locking is to reserve a single 
byte at the beginning of each data record. This byte 
indicates whether or not the record is currently locked. 
Perhaps an "L" indicates this. Therefore, to lock a record, 
you first retrieve it and check to see if is already locked. 
If not, assign an "L" and write the record back. Any other 
computer can tell if that record is locked by examining the 
first byte. An enhancement to this approach might also store 
user identification.

---------------------------------------------------------------------------
Dean Hodgson
BookMark Project -- School Library Automation Software
Department of Education, Training and Employment  Adelaide, South Australia
email to:  dhodgson@nexus.edu.au
website:   http://www.nexus.edu.au/bookmark/
phone 0011-61-8-8226-1541  fax 0011-61-8-8410-2856
---------------------------------------------------------------------------
---------------------------------------------------------
 Newsletter compiled and edited by: Brosco and Alyce.
 Comments, requests or corrections: Hit 'REPLY' now!
            mailto:brosco@orac.net.au
                       or
           mailto:awatson@mail.wctc.net
---------------------------------------------------------