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

前端 未结 2 1010
花落未央
花落未央 2021-01-14 12:01

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

2条回答
  •  被撕碎了的回忆
    2021-01-14 12:28

    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
    

提交回复
热议问题