Visual Basic File API Routines
SetEndOfFile: Delete Records from the End of a Random Access File
Posted:   Monday January 30, 2006
Updated:   Monday December 26, 2011
Applies to:   VB4-32, VB5, VB6
Developed with:   VB6, Windows XP
OS restrictions:   None
Author:   VBnet - Randy Birch


PathMatchSpec: Searches a String Using MS-DOS Wild-Card Match
PathFindExtension: File Extension from Local/Remote/IP/UNC Filename

PathFindFileName: File Path from Local/Remote/IP/UNC Filename

PathFindNextComponent: Parse File Path Components

PathFindNextComponent: Parse String to Obtain Path Components

PathRemoveExtension: Returns Filename with Extension Removed

PathStripPath: Removes Path Portion of Fully-qualified Path/Filename

PathUnquoteSpaces: Removes Wrapping Quotes from Paths/Filenames

PathStripToRoot: Remove All Parts of a Path Except for Root Information

PathSkipRoot: Parses Path Ignoring Drive or UNC Path

PathStripToRoot: Determine if a Path Contains a Valid Drive Syntax

PathIsSameRoot: Determine if Paths Share Common Root

PathQuoteSpaces: Returns Quoted Path String if Spaces Found

VB's Random Access file routines provides a powerful way to create well defined file structures and to access that data without the restrictions applied to normal sequential file operations. Random Access files are particularly well suited for applications that do not require the weight and complexity of a database back end, such as a personal contact manager or other similar application.

One of the difficulties encountered in using random access files is the deletion of outdated records. While the conventional and recommended programming practice is to include a flag member in the UDT that defines the file record that can be flipped to indicate that - on the next save - the particular record should be overwritten with the data of a new record. And while this is certainly a recommended practice (as compared with the only other option of writing the existing data to a new file up top the record to be deleted, skipping that record, and continuing to the end of the file), having the ability to delete records from the end of a random access file is not outside the abilities of a few API calls.

Random access files - along with the methods to manipulate random access file data - is unique to VB; Windows knows nothing about random access file access, and as such the Windows APIs for writing to files can't manipulate the file's data in the same way that VB can.  But Windows does provide a specific API - SetEndOfFile - that can be used against a random access file to truncate one or more records.

The demo as coded below does three things:

  • creates a 15 record random access file
  • provides a means to read and output the file's data to the immediate window
  • deletes the last record each time the delete button is pressed.

The structure and layout of the UDT for this demo was made to provide you with an easy-to-see confirmation of the start and end data for each record when printed to the immediate window. Once you've reached the second step, the records will display data similar to:

begin record 1
30 Jan 2006 8:11:45 PM
the end of 1
begin record 2
30 Jan 2006 8:11:45 PM
the end of 2
begin record 3

... and so on through:

the end of 14
begin record 15
30 Jan 2006 8:11:45 PM
the end of 15

When the first truncation takes place, the file will be cut immediately before "begin record 15", and so on for each subsequent press. Given the code and comments, it would be a small task to alter the routine to accept a value that indicated the number of records to delete from the end of the file other than just one, since the number of records to delete times the size of one record is all that would need to be subtracted from the file total size to affect his modification.

 BAS Module Code

 Form Code
Add three command buttons (Command1 / Command2 / Command3) to a form along with the following code:

Option Explicit
' Copyright 1996-2011 VBnet/Randy Birch, All Rights Reserved.
' Some pages may also contain other copyrights by the author.
' Distribution: You can freely use this code in your own
'               applications, but you may not reproduce 
'               or publish this code on any web site,
'               online service, or distribute as source 
'               on any media without express permission.
'change this to a suitable location on your system!
Private Const sTestFile As String = "d:\test.txt"

Private Const OPEN_ALWAYS As Long = 4
Private Const FILE_BEGIN As Long = 0
Private Const GENERIC_WRITE As Long = &H40000000
Private Const GENERIC_READ As Long = &H80000000
Private Const FILE_ATTRIBUTE_NORMAL As Long = &H80

'test file UDT
Private Type TestData
   s1 As String * 15
   s2 As String * 5
   dw1 As Long
   dt1 As Date
   dw2 As Long
   s3 As String * 13
End Type

Private Declare Function CreateFile Lib "kernel32" _
   Alias "CreateFileA" _
  (ByVal lpFileName As String, _
   ByVal dwDesiredAccess As Long, _
   ByVal dwShareMode As Long, _
   ByVal lpSecurityAttributes As Long, _
   ByVal dwCreationDisposition As Long, _
   ByVal dwFlagsAndAttributes As Long, _
   ByVal hTemplateFile As Long) As Long

Private Declare Function GetFileSize Lib "kernel32" _
   (ByVal hfile As Long, lpFileSizeHigh As Long) As Long

Private Declare Function SetFilePointer Lib "kernel32" _
   (ByVal hfile As Long, _
    ByVal lDistanceToMove As Long, _
    lpDistanceToMoveHigh As Long, _
    ByVal dwMoveMethod As Long) As Long

Private Declare Function SetEndOfFile Lib "kernel32" _
  (ByVal hfile As Long) As Long

Private Declare Function CloseHandle Lib "kernel32" _
   (ByVal hfile As Long) As Long

Private Sub Form_Load()

   Command1.Caption = "Create random file"
   Command2.Caption = "Read random file data"
   Command3.Caption = "Delete last random record"

End Sub

Private Sub Command1_Click()

   Dim td As TestData
   Dim nodata As TestData
   Dim cnt As Long
   Dim hfile As Long

   hfile = FreeFile
   Open sTestFile For Random Access Write As #hfile Len = Len(td)

    'add 15 random records to the test file
      For cnt = 1 To 15

         With td
           'for debug purposes, this line is
           'the first line of each record written
            .s1 = "begin record " & cnt
           'for debug purposes, this line is
           'the last line of each record written
            RSet .s3 = "the end of " & cnt
           'some other random data for the record
            .s2 = String(Len(.s1), cnt + 96)
            .dw1 = 1234
            .dw2 = 3445
            .dt1 = Now
         End With
         Put #hfile, cnt, td
         td = nodata
      Next cnt
   Close #hfile
   Debug.Print "file created"
End Sub

Private Sub Command2_Click()

   Dim td As TestData
   Dim nodata As TestData
   Dim cnt As Long
   Dim hfile As Long
   Dim totalrecords As Long

   hfile = FreeFile
   Open sTestFile For Random Access Read As #hfile Len = Len(td)

      totalrecords = LOF(hfile) \ Len(td)
      For cnt = 1 To totalrecords
         Get #hfile, cnt, td
         With td
            Debug.Print .s1
            Debug.Print .s2
            Debug.Print .dw1
            Debug.Print .dw2
            Debug.Print .dt1
            Debug.Print .s3
         End With
         td = nodata
      Next cnt
   Close #hfile
End Sub

Private Sub Command3_Click()
End Sub

Private Sub DeleteLastRandomRecord()

   Dim td As TestData
   Dim hfile As Long
   Dim totalrecords As Long
   Dim newfilesize As Long
   Dim dwFileSizeLow As Long
   Dim dwFileSizeHigh As Long
  'how many records in the file?
   totalrecords = FileLen(sTestFile) \ Len(td)
  'can't truncate the file if
  'totalrecords is less than 2
   If totalrecords >= 2 Then
     'open the file using the API
      hfile = CreateFile(sTestFile, _
                          GENERIC_WRITE Or GENERIC_READ, _
                          0&, ByVal 0&, _
                          OPEN_ALWAYS, _
                          FILE_ATTRIBUTE_NORMAL, _
     'Get the file size. Note that the value
     'returned is the size of the file as
     'stored in the low word of GetFileSize with
     'the high word returning 0. This will
     'accommodate most reasonably-sized files.
     'If the file is large, however, then the low
     'and high word values have to be be combined
     'to get the actual size. This demo assumes a
     'you're using a reasonably sized random access
     'file, so a test of dwFileSizeHigh is made
     'to ensure it is 0 before truncating.
      dwFileSizeLow = GetFileSize(hfile, dwFileSizeHigh)
      If dwFileSizeHigh = 0 Then
        'dwFileSizeLow for this test is the
        'file size; one record is Len(td),
        'so in order to delete the last
        'record the new size is the current
        'size minus the length of one record:
         newfilesize = dwFileSizeLow - Len(td)
         Debug.Print totalrecords, dwFileSizeLow, newfilesize
        'now move the file pointer to the
        'new end position ..
         If SetFilePointer(hfile, newfilesize, dwFileSizeHigh, FILE_BEGIN) > 0 Then
           'and "delete" the last record by
           'changing the end of file marker
            Call SetEndOfFile(hfile)
         End If  'SetFilePointer
      End If  'dwFileSizeHigh

      CloseHandle hfile
   End If
End Sub


PayPal Link
Make payments with PayPal - it's fast, free and secure!


Copyright 1996-2011 VBnet and Randy Birch. All Rights Reserved.
Terms of Use  |  Your Privacy


Hit Counter