Is there a way to navigate backwards in a TextStream file?

2.6k views Asked by At

I have started to use the fso object in order to overcome the 2GB limit of VBA. Everything looks satisfactory for my purposes, except that I can not find a way to go backwards in the textstream files. For going forward I have used read(no of chars) and skip(no of chars). Is there a way to do so ?

2

There are 2 answers

7
Blackhawk On BEST ANSWER

I ran into the same frustrating limitation. Here is a class that wraps the native Windows API to perform File IO. As noted, it is based on the example on msdn at http://support.microsoft.com/kb/189981. I haven't finished testing it thoroughly, so if you find any issues, let me know so I can fix them for both our benefit. As a side note, the CanRead, CanWrite stuff is there so I can eventually implement a stream interface, but that's a future project.

Option Compare Database
Option Explicit

'Based on the example on msdn:
'http://support.microsoft.com/kb/189981

'Some of the constants come from Winnt.h

Public Enum FileAccess
'    FILE_READ_DATA = &H1                     ' winnt.h:1801
'    'FILE_LIST_DIRECTORY = &H1                ' winnt.h:1802
'    FILE_WRITE_DATA = &H2                    ' winnt.h:1804
'    'FILE_ADD_FILE = &H2                      ' winnt.h:1805
'    FILE_APPEND_DATA = &H4                   ' winnt.h:1807
'    'FILE_ADD_SUBDIRECTORY = &H4              ' winnt.h:1808
'    'FILE_CREATE_PIPE_INSTANCE = &H4          ' winnt.h:1809
'    FILE_READ_EA = &H8                       ' winnt.h:1811
'    FILE_READ_PROPERTIES = &H8               ' winnt.h:1812
'    FILE_WRITE_EA = &H10                     ' winnt.h:1814
'    FILE_WRITE_PROPERTIES = &H10             ' winnt.h:1815
'    FILE_EXECUTE = &H20                      ' winnt.h:1817
'    'FILE_TRAVERSE = &H20                     ' winnt.h:1818
'    'FILE_DELETE_CHILD = &H40                 ' winnt.h:1820
'    FILE_READ_ATTRIBUTES = &H80              ' winnt.h:1822
'    FILE_WRITE_ATTRIBUTES = &H100            ' winnt.h:1824
    FILE_ALL_ACCESS = &H1F01FF               ' winnt.h:1826
    FILE_GENERIC_READ = &H120089             ' winnt.h:1828
    FILE_GENERIC_WRITE = &H120116            ' winnt.h:1835
'    FILE_GENERIC_EXECUTE = &H1200A0          ' winnt.h:1843
'    FILE_SHARE_READ = &H1                    ' winnt.h:1848
'    FILE_SHARE_WRITE = &H2                   ' winnt.h:1849
'    FILE_NOTIFY_CHANGE_FILE_NAME = &H1       ' winnt.h:1860
'    FILE_NOTIFY_CHANGE_DIR_NAME = &H2        ' winnt.h:1861
'    FILE_NOTIFY_CHANGE_ATTRIBUTES = &H4      ' winnt.h:1862
'    FILE_NOTIFY_CHANGE_SIZE = &H8            ' winnt.h:1863
'    FILE_NOTIFY_CHANGE_LAST_WRITE = &H10     ' winnt.h:1864
'    FILE_NOTIFY_CHANGE_SECURITY = &H100      ' winnt.h:1865
'    'MAILSLOT_NO_MESSAGE = -1                ' winnt.h:1866
'    'MAILSLOT_WAIT_FOREVER = -1              ' winnt.h:1867
'    FILE_CASE_SENSITIVE_SEARCH = &H1         ' winnt.h:1868
'    FILE_CASE_PRESERVED_NAMES = &H2          ' winnt.h:1869
'    FILE_UNICODE_ON_DISK = &H4               ' winnt.h:1870
'    FILE_PERSISTENT_ACLS = &H8               ' winnt.h:1871
'    FILE_FILE_COMPRESSION = &H10             ' winnt.h:1872
'    FILE_VOLUME_IS_COMPRESSED = &H8000       ' winnt.h:1873
'    IO_COMPLETION_MODIFY_STATE = &H2         ' winnt.h:1874
'    IO_COMPLETION_ALL_ACCESS = &H1F0003      ' winnt.h:1875
'    DUPLICATE_CLOSE_SOURCE = &H1             ' winnt.h:1876
'    DUPLICATE_SAME_ACCESS = &H2              ' winnt.h:1877
'    DELETE = &H10000                         ' winnt.h:1935
'    READ_CONTROL = &H20000                   ' winnt.h:1936
'    WRITE_DAC = &H40000                      ' winnt.h:1937
'    WRITE_OWNER = &H80000                    ' winnt.h:1938
'    SYNCHRONIZE = &H100000                   ' winnt.h:1939
'    STANDARD_RIGHTS_REQUIRED = &HF0000       ' winnt.h:1941
'    STANDARD_RIGHTS_READ = &H20000           ' winnt.h:1943
'    STANDARD_RIGHTS_WRITE = &H20000          ' winnt.h:1944
'    STANDARD_RIGHTS_EXECUTE = &H20000        ' winnt.h:1945
'    STANDARD_RIGHTS_ALL = &H1F0000           ' winnt.h:1947
'    SPECIFIC_RIGHTS_ALL = &HFFFF             ' winnt.h:1949
'    ACCESS_SYSTEM_SECURITY = &H1000000
End Enum


Public Enum FileShare
    NONE = &H0
    FILE_SHARE_DELETE = &H4
    FILE_SHARE_READ = &H1
    FILE_SHARE_WRITE = &H2
End Enum


Public Enum FileCreationDisposition
    CREATE_ALWAYS = &H2
    CREATE_NEW = &H1
    OPEN_ALWAYS = &H4
    OPEN_EXISTING = &H3
    TRUNCATE_EXISTING = &H5
End Enum


'Public Enum FileFlagsAndAttributes
'    'Attributes
'    FILE_ATTRIBUTE_ENCRYPTED = &H4000
'    FILE_ATTRIBUTE_READONLY = &H1            ' winnt.h:1850
'    FILE_ATTRIBUTE_HIDDEN = &H2              ' winnt.h:1851
'    FILE_ATTRIBUTE_SYSTEM = &H4              ' winnt.h:1852
'    FILE_ATTRIBUTE_DIRECTORY = &H10          ' winnt.h:1853
'    FILE_ATTRIBUTE_ARCHIVE = &H20            ' winnt.h:1854
'    FILE_ATTRIBUTE_NORMAL = &H80             ' winnt.h:1855
'    FILE_ATTRIBUTE_TEMPORARY = &H100         ' winnt.h:1856
'    FILE_ATTRIBUTE_ATOMIC_WRITE = &H200      ' winnt.h:1857
'    FILE_ATTRIBUTE_XACTION_WRITE = &H400     ' winnt.h:1858
'    FILE_ATTRIBUTE_COMPRESSED = &H800        ' winnt.h:1859
'    'Flags
'    FILE_FLAG_BACKUP_SEMANTICS = &H2000000
'    FILE_FLAG_DELETE_ON_CLOSE = &H4000000
'    FILE_FLAG_NO_BUFFERING = &H20000000
'    FILE_FLAG_OPEN_NO_RECALL = &H100000
'    FILE_FLAG_OPEN_REPARSE_POINT = &H200000
'    FILE_FLAG_OVERLAPPED = &H40000000
'    FILE_FLAG_POSIX_SEMANTICS = &H100000
'End Enum


Private Const INVALID_FILE_HANDLE = -1 '&HFFFFFFFF
Private Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000
Private Const INVALID_FILE_SIZE As Long = -1 '&HFFFFFFFF
Private Const INVALID_SET_FILE_POINTER As Long = -1 '&HFFFFFFFF


Private Declare Function FormatMessage Lib "Kernel32" Alias "FormatMessageA" (ByVal dwFlags As Long, _
                                                                              lpSource As Long, _
                                                                              ByVal dwMessageId As Long, _
                                                                              ByVal dwLanguageId As Long, _
                                                                              ByVal lpBuffer As String, _
                                                                              ByVal nSize As Long, _
                                                                              Arguments As Any) As Long


Private Declare Function CreateFile Lib "Kernel32" Alias "CreateFileA" (ByVal lpFileName As String, _
                                                                        ByVal dwDesiredAccess As Long, _
                                                                        ByVal dwShareMode As Long, _
                                                                        lpSecurityAttributes As Long, _
                                                                        ByVal dwCreationDisposition As Long, _
                                                                        ByVal dwFlagsAndAttributes As Long, _
                                                                        hTemplateFile 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 ReadFile Lib "Kernel32" (ByVal hFile As Long, _
                                                  lpBuffer As Any, _
                                                  ByVal nNumberOfBytesToRead As Long, _
                                                  lpNumberOfBytesRead As Long, _
                                                  ByVal lpOverlapped As Long) As Long


Private Declare Function WriteFile Lib "Kernel32" (ByVal hFile As Long, _
                                                   lpBuffer As Any, _
                                                   ByVal nNumberOfBytesToWrite As Long, _
                                                   lpNumberOfBytesWritten As Long, _
                                                   ByVal lpOverlapped As Long) As Long


Private Declare Function FlushFileBuffers Lib "Kernel32" (ByVal hFile As Long) As Long


Private Declare Function GetFileSize Lib "Kernel32" (ByVal hFile As Long, _
                                                     lpFileSizeHigh As Long) As Long


Private Declare Function CloseHandle Lib "Kernel32" (ByVal hObject As Long) As Long

Private m_Handle As Long

Private Sub Class_Terminate()
    If Not m_Handle = 0 Then
        Flush
        CloseFile
    End If
End Sub

Public Sub OpenFile(path As String, Optional access As FileAccess = FileAccess.FILE_GENERIC_READ, Optional share As FileShare = FileShare.NONE, Optional CreationDisposition As FileCreationDisposition = FileCreationDisposition.OPEN_ALWAYS)
    Dim Ret As Long
    Ret = CreateFile(path, access, share, ByVal 0&, CreationDisposition, 0&, ByVal 0&)
    If Ret = INVALID_FILE_HANDLE Then
        Err.Raise vbObjectError + Err.LastDllError, "clsFile.OpenFile", DecodeAPIErrors(Err.LastDllError)
    Else
        m_Handle = Ret
    End If
End Sub

'Properties

Public Property Get Length() As Double
    Dim Ret As Currency
    Dim FileSizeHigh As Long
    Ret = GetFileSize(m_Handle, FileSizeHigh)
    If Not Ret = INVALID_FILE_SIZE Then
        Length = Ret
    Else
        Err.Raise vbObjectError + Err.LastDllError, "clsFile.Length", DecodeAPIErrors(Err.LastDllError)
    End If
End Property

Public Property Get Position() As Long
    Dim Ret As Long
    Dim DistanceToMoveHigh As Long
    Ret = SetFilePointer(m_Handle, 0&, DistanceToMoveHigh, 1&) '1 is FILE_CURRENT
    If DistanceToMoveHigh = 0 Then
        If Ret = -1 Then
            Position = -1 'EOF'
        Else
            Position = Ret
        End If
    Else
        Class_Terminate
        Err.Raise vbObjectError + Err.LastDllError, "clsFile.Position", DecodeAPIErrors(Err.LastDllError)
    End If
End Property

Public Property Get Handle() As Long
    Handle = m_Handle
End Property

'Functions

Public Function ReadBytes(ByRef buffer() As Byte, ByVal buffer_offset As Long, ByVal count As Long) As Long
    Dim Ret As Long
    Dim BytesRead As Long
    Ret = ReadFile(m_Handle, buffer(buffer_offset), count, BytesRead, 0&)
    If Ret = 1 Then
        ReadBytes = BytesRead
    Else
        Class_Terminate
        Err.Raise vbObjectError + Err.LastDllError, "clsFile.ReadBytes", DecodeAPIErrors(Err.LastDllError)
    End If
End Function

Public Function ReadBytesPtr(ByVal ptrBuf As Long, ByVal buffer_offset As Long, ByVal count As Long) As Long
    Dim Ret As Long
    Dim BytesRead As Long
    Ret = ReadFile(m_Handle, ByVal ptrBuf + buffer_offset, count, BytesRead, 0&)
    If Ret = 1 Then
        ReadBytesPtr = BytesRead
    Else
        Class_Terminate
        Err.Raise vbObjectError + Err.LastDllError, "clsFileStream.ReadBytesPtr", DecodeAPIErrors(Err.LastDllError)
    End If
End Function

Public Function WriteBytes(ByRef buffer() As Byte, ByVal buffer_offset As Long, ByVal count As Long) As Long
    Dim Ret As Long
    Dim BytesWritten As Long
    Ret = WriteFile(m_Handle, buffer(buffer_offset), count, BytesWritten, 0&)
    If Ret = 1 Then
        WriteBytes = BytesWritten
    Else
        Class_Terminate
        Err.Raise vbObjectError + Err.LastDllError, "clsFile.WriteBytes", DecodeAPIErrors(Err.LastDllError)
    End If
End Function

Public Function WriteBytesPtr(ByVal ptrBuf As Long, ByVal buffer_offset As Long, ByVal count As Long) As Long
    Dim Ret As Long
    Dim BytesWritten As Long
    Ret = WriteFile(m_Handle, ByVal ptrBuf + buffer_offset, count, BytesWritten, 0&)
    If Ret = 1 Then
        WriteBytesPtr = BytesWritten
    Else
        Class_Terminate
        Err.Raise vbObjectError + Err.LastDllError, "clsFile.WriteBytes", DecodeAPIErrors(Err.LastDllError)
    End If
End Function

Public Function SeekFile(ByVal LoBytesOffset As Long, origin As SeekOrigin) As Long
    Dim Ret As Long
    Dim HiBytesOffset As Long
    Ret = SetFilePointer(m_Handle, LoBytesOffset, HiBytesOffset, origin)
    If Not Ret = INVALID_SET_FILE_POINTER Then
        SeekFile = Ret
    Else
        Err.Raise vbObjectError + Err.LastDllError, "clsFile.SeekFile", DecodeAPIErrors(Err.LastDllError)
    End If
End Function

Public Function SeekFile64bit(ByVal offset As Currency, origin As SeekOrigin) As Currency
'Take care with this function. A Currency variable is an 8-byte (64-bit) scaled (by 10,000) fixed-point number.'
'This means that setting a Currency variable to 0.0001 is the equivalent of a binary value of 1.'
'If you want to set an offset with an immediate value, write it like so:'
'1073741824 Bytes (1 GB) would be 107374.1824@, where @ is the symbol for an immediate Currency value.'
'Refer to http://support.microsoft.com/kb/189862 for hints on how to do 64-bit arithmetic'
    Dim Ret As Long
    Dim curFilePosition As Currency
    Dim LoBytesOffset As Long, HiBytesOffset As Long

    CopyMemory VarPtr(HiBytesOffset), VarPtr(offset) + 4, 4
    CopyMemory VarPtr(LoBytesOffset), VarPtr(offset), 4

    Ret = SetFilePointer(m_Handle, LoBytesOffset, HiBytesOffset, origin)

    CopyMemory VarPtr(curFilePosition) + 4, VarPtr(HiBytesOffset), 4
    CopyMemory VarPtr(curFilePosition), VarPtr(Ret), 4

    SeekFileCurrency = curFilePosition
End Function

Public Sub CloseFile()
    Dim Ret As Long
    Ret = CloseHandle(m_Handle)
    m_Handle = 0
End Sub

Public Sub Flush()
    Dim Ret As Long
    Ret = FlushFileBuffers(m_Handle)
End Sub

 '***********************************************************************************
' Helper function, from Microsoft page as noted at top
Private Function DecodeAPIErrors(ByVal ErrorCode As Long) As String
    Dim sMessage As String, MessageLength As Long
    sMessage = Space$(256)
    MessageLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0&, _
                                  ErrorCode, 0&, sMessage, 256&, 0&)
    If MessageLength > 0 Then
        DecodeAPIErrors = Left(sMessage, MessageLength)
    Else
        DecodeAPIErrors = "Unknown Error."
    End If
End Function

And here's an example of how to use it:

Public Sub Main()
    Dim oFile As clsFile
    Set oFile = New clsFile

    oFile.OpenFile "C:\YourFilePathHere", FILE_GENERIC_READ, NONE, OPEN_EXISTING

    Dim ChunkOfData() As Byte
    Const CHUNKSIZE As Long = 4096
    ReDim ChunkOfData(0 To CHUNKSIZE - 1)

    Dim lngCurrChunk As Long
    Dim lngBytesRead As Double


    'The SeekFile function works for seeks forward or backward in the file from [-2GB to +2GB).'
    'Past that you can use the SeekFile64bit function, but you'll have to be aware of the issues with using Currency to store the 64-bit number'
    Debug.Print oFile.SeekFile(&H40000000, so_Current) 'A 1GB seek

    lngBytesRead = oFile.ReadBytes(ChunkOfData, 0, CHUNKSIZE)
    While lngBytesRead > 0 'As soon as a call to ReadBytes returns 0, we've reached the end of the file.
        'Do something with the 4k chunk of data.  The buffer gets reused in this example.
        'Debug.Print ChunkOfData
        lngCurrChunk = lngCurrChunk + 1
        lngBytesRead = oFile.ReadBytes(ChunkOfData, 0, CHUNKSIZE)
    Wend
    MsgBox "Complete!"
End Sub
0
Dmitry On

Try ADODB.Stream. Here are a couple of links: MSDN and W3