Matthew
Matthew

Reputation: 4056

How to correctly read a random access file in VB.NET

I am attempting to read a random access file, but I am getting the following error on the first file Error 5 (unable to read beyond end of the stream). I am not sure what I am doing wrong here, how might I fix this issue?

Structure StdSections
    'UPGRADE_WARNING: Fixed-length string size must fit in the buffer. Click for more: 'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="3C1E4426-0B80-443E-B943-0627CD55D48B"'
    <VBFixedString(15), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=15)> Public A() As Char 'BEAM  --- complete beam designation          15
    'UPGRADE_WARNING: Fixed-length string size must fit in the buffer. Click for more: 'ms-help://MS.VSCC.v90/dv_commoner/local/redirect.htm?keyword="3C1E4426-0B80-443E-B943-0627CD55D48B"'
    <VBFixedString(2), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=2)> Public B() As Char 'DSG   --- shape  ie "W" or "C"                2
    Dim C As Single 'DN    --- nominal depth of section            4
    Dim d As Single 'WGT   --- weight                              4
    .
    .
    .
End structure
''Note 'File1'is the existing RAF and holds complete path!

        Dim i,ffr,fLength,lastmembNo as integer
        sectionFound = False
        Dim std As new StdSections 
        fLength = Len(std)
        If fLength = 0 Then fLength = 168 ' 177
        ffr = FreeFile()
        FileOpen(ffr, File1, OpenMode.Random, OpenAccess.Read, OpenShare.LockRead, fLength)
        lastmembNo = CInt(LOF(ffr)) \ fLength

        For i = 1 To lastmembNo
            FileGet(ffr, std, i)
            >>Error 5 (unable to read beyond end of the stream) <<<                  
            If Trim(memberID) = Trim(std.A) Then
                    sectionFound = True
                end if
        next i

Upvotes: 1

Views: 10088

Answers (3)

user6154528
user6154528

Reputation:

I'm not sure about your example but this one however, works:

Public Class Form1

Const maxLenName = 30

Structure person
    <VBFixedString(maxLenName)> Dim name As String
    Dim age As Byte
End Structure

Private Sub Form1_Load(sender As [Object], e As EventArgs) Handles MyBase.Load

    Dim entryIn As New person
    Dim recordLen As Integer = Len(entryIn)
    Dim entry As person

    If FileIO.FileSystem.FileExists("test.raf") Then Kill("test.raf")
    FileOpen(1, "test.raf", OpenMode.Random,,, recordLen)

    'write
    entry.name = LSet("Bill", maxLenName)
    entry.age = 25
    FilePut(1, entry, 6) 'write to 6th record

    'read
    Dim nRecords As Integer = LOF(1) \ recordLen
    FileGet(1, entryIn, nRecords)
    FileClose(1)

    Dim personName As String = RTrim(entryIn.name)
    Dim personAge As Byte = entryIn.age

    MsgBox(personName & "'s age is " & personAge)
End Sub
End Class

Upvotes: 0

SSS
SSS

Reputation: 5393

OK, I think you should switch to the ".NET way", as follows:

Imports System.IO
Imports System.Xml

Public Class Form1

  Public Const gintRecLen_CONST As Integer = 177

  Class StdSections2
    Private mstrA As String
    Public Property A() As String
      Get
        Return mstrA
      End Get
      Set(ByVal value As String)
        If value.Length <> 15 Then
          Throw New Exception("Wrong size")
        End If
        mstrA = value
      End Set
    End Property

    Private mstrB As String
    Public Property B() As String
      Get
        Return mstrB
      End Get
      Set(ByVal value As String)
        If value.Length <> 2 Then
          Throw New Exception("Wrong size")
        End If
        mstrB = value
      End Set
    End Property

    Public C(39) As Single

    Public Shared Function FromBytes(byt() As Byte) As StdSections2
      Dim output As New StdSections2

      If byt.Length <> gintRecLen_CONST Then
        Throw New Exception("Wrong size")
      End If
      For i As Integer = 0 To 14
        output.mstrA &= Chr(byt(i))
      Next i
      For i As Integer = 15 To 16
        output.mstrB &= Chr(byt(i))
      Next i
      For i As Integer = 0 To 39
        Dim bytTemp(3) As Byte
        output.C(i) = BitConverter.ToSingle(byt, 17 + 4 * i)
      Next i
      Return output
    End Function
  End Class

  Sub New()

    ' This call is required by the designer.
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.


  End Sub


  Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    Dim strFilename As String = "C:\Junk\Junk.txt"
    Dim strMemberID As String = "foo"
    Dim intRecCount As Integer = CInt(My.Computer.FileSystem.GetFileInfo(strFilename).Length) \ gintRecLen_CONST
    Dim blnSectionFound As Boolean = False
    Using fs As New FileStream(strFilename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
      For intRec As Integer = 0 To intRecCount - 1
        Dim intRecPos As Integer = gintRecLen_CONST * intRec
        fs.Seek(intRecPos, SeekOrigin.Begin)
        Dim byt(gintRecLen_CONST - 1) As Byte
        fs.Read(byt, 0, gintRecLen_CONST)
        Dim ss2 As StdSections2 = StdSections2.FromBytes(byt)
        'MsgBox(ss2.A & ":" & ss2.C(3)) 'debugging
        If strMemberID.Trim = ss2.A.Trim Then
          blnSectionFound = True
          Exit For
        End If
      Next intRec
    End Using
    MsgBox(blnSectionFound.ToString)

  End Sub
End Class

We define a class called StdSections2 which uses .NET strings and an array of Singles. We use 0-based arrays everywhere. We load the file using the new FileStream object, and use the Seek() command to find the position we want. We then load raw bytes out of the file, and use Chr() and BitConverter.ToSingle() to convert the raw bytes into strings and singles.

Upvotes: 0

SSS
SSS

Reputation: 5393

Wow Freefile! That's a blast from the past!

I haven't really used the old OpenFile etc. file access methods in VB.NET, so I'm just speculating, but in .NET many of the variable types got changed in size. e.g. an Integer is now 32-bits (4 bytes), I think a Boolean is different, though a Single is still 4 bytes.

Also, strings in .NET are by default in Unicode, not ASCII, so you cannot rely on 1 character=1 byte in a .NET String variable. In fact, .NET actualy "JIT compiles" programs on the PC before running, so you can't really lay out structures in memory easily like the old days.

If you want to switch to the new "Stream" based objects, here's some code to get you started:

    Dim strFilename As String = "C:\Junk\Junk.txt"
    Dim strTest As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    Call My.Computer.FileSystem.WriteAllText(strFilename, strTest, False)
    Dim byt(2) As Byte
    Using fs As New FileStream(strFilename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)
      fs.Seek(16, SeekOrigin.Begin)
      fs.Read(byt, 0, 3)
      Dim s As String = Chr(byt(0)) & Chr(byt(1)) & Chr(byt(2))
      MsgBox(s)
      fs.Seek(5, SeekOrigin.Begin)
      fs.Write(byt, 0, 3)
    End Using
    Dim strModded As String = My.Computer.FileSystem.ReadAllText(strFilename)
    MsgBox(strModded)

I wouldn't blame you for keeping the old method though: with the new method, you'll need to define a class, and then have a custom routine to convert from Byte() to the properties of the class. More work than simply loading bytes from the file into memory.

Upvotes: 1

Related Questions