Liberty Basic is develeopped by Carl Gundel
Original Newsletter compiled by Alyce Watson and Brosco
Translation to HTML: Raymond Roumeas

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 dhodgso-@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 LB’s 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 LB’s 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 identificati


Dean Hodgson BookMark Project -- School Library Automation Software Department of Education, Training and Employment Adelaide, South Australia email to: dhodgso-@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:brosc-@orac.net.au

or

mailto:awatso-@mail.wctc.net