3

I am recreating an older VB6 program that reads random-access files created using another VB6 program in VB.net. Backwards compatibility is essential for the new VB.net program. There are thousands of files that have been written that need to be accessed. There are five lines to each of the files when I open them in notepad++, though I can't make heads or tails of the random characters in notepad++. The files should contain four records, so I am not sure what the fifth line is for.

I have access to the old source code for both programs. Below are the VB6 read and write methods.

VB6 Write Method

Dim fi as long
fi = FreeFile
Open fileName For Random As #fi Len = 32000

Put #fi, 1, Loads
Put #fi, 2, Nodes
Put #fi, 3, Names
Put #fi, 4, Options

Close fi

VB6 Read Method

Dim fi As Long      
fi = FreeFile
Open fileName For Random As #fi Len = 32000

Get #fi, 1, Loads
Get #fi, 2, Nodes
Get #fi, 3, Names
Get #fi, 4, Options

Close fi

VB,net Read Method (attempt)
In VB.net, I have the following that I am using to try to read the same files.

Dim fi As Integer = FreeFile()
FileOpen(fi, fileName, OpenMode.Random, RecordLength:=32000)

FileGet(fi, Loads, 1)
FileGet(fi, Nodes, 2)
FileGet(fi, Names, 3)
FileGet(fi, Options, 4)

FileClose(fi)

Loads, Nodes, Names, and Options are [user-defined] types in VB6, and structures in VB.net. These structures often have other structures inside of them, and those structures contain more structures. Some of the structures in the hierarchy contain arrays of other structures, which may have arrays of yet more structures inside of them. Most structure fields were singles or booleans. There were a few longs in the VB6 that I switched to be integers in VB.net (to keep the byte size the same). There are a few strings fields as well.

When I try to use the VB.net read method above, it gets values for Loads, Nodes, and Names. On the line FileGet(fi, Options, 4), I get: Systems.IO.EndOfStreamException: 'Unable to read beyond the end of the stream.'

Looking at the two methods to read above, shouldn't they work the same? What exactly is causing this exception in vb.net? Is the record size not <= 32,000 bytes?


Edit

Options is based off typOptions, which uses typDesign and typGeometry. VB6:

Public Const numDebug = 12
Public Type typOptions
    optDesign As typDesign
    optDebug(numDebug) As Boolean
    optGeom As typGeometry
    optFrac As Single
End Type
Public Type typDesign
    mem01 As Single
    mem02 As Single
    mem03 As Single
    mem04 (3) As Single
End Type
Public Type typGeometry
    member1 As Single
    member2 As Single
    member3 As Single
    member4 As Single
    member5 As Single
    member6 As Single
    member7 As Single
End Type

VB.net

Public Const numDebug = 12
Public Structure typOptions
    Public optDesign As typDesign
    Public optDebug() As Boolean
    Public optGeom As typGeometry
    Public optFrac As Single
End Structure
Public Structure typDesign
    Public mem01 As Single
    Public mem02 As Single
    Public mem03 As Single
    Public mem04 () As Single
End Structure
Public Structure typGeometry
    Public member1 As Single
    Public member2 As Single
    Public member3 As Single
    Public member4 As Single
    Public member5 As Single
    Public member6 As Single
    Public member7 As Single
End Structure
' Elsewhere, when the form is loading:
ReDim Options.optDesign.mem04 (3)
ReDim Options.optDebug(numDebug)

EDIT 2

The file size for the files seems to be consistently 96,124 bytes. Only 124 bytes are being stored for Options instead of 32,000. I tried using a hex editor and adding "00" until there was 128,000 bytes (32,000 bytes per record * 4 records), but then I still get the Unable to read beyond the end of the stream exception. The 128,000 byte file did work in VB6 though.

I found some information about this from vbmigration.com:

VB6 and VB.NET greatly differ in how UDTs - Structures in VB.NET parlance - are read from or written to files. Not only are structure elements stored to file in a different format, but the two languages also manage the End-of-File condition in a different way. In VB6 you can read a UDT even if the operation would move the file pointer beyond the current file’s length; in VB.NET such an operation would cause an exception.

Based on that, I would have thought that making the file 128,000 bytes instead of 96,124 would have fixed the unable to read beyond the end of stream exception.

VeryColdAir
  • 107
  • 2
  • 8
  • 1
    (The file is binary data and not meant to be read with a text editor, so you can ignore that there appear to be five lines.) – Andrew Morton Nov 08 '21 at 18:12
  • Are you reading into `Options` as defined by vb.net, or defined in VB6, i.e. importing the type from VB6 via ActiveX? Also are you able to change the order of the files written and read to see if it still fails on Options or always on the last one? – djv Nov 08 '21 at 18:44
  • 1
    @djv Always fails on Options. Options is defined in a VB.net module of the project, I didn't know it was possible to import types from VB6. – VeryColdAir Nov 08 '21 at 19:09
  • I would guess you have some type mismatch between VB6 and vb.net then. Figure out what is unique about Options, see if you can address any differences. Yes you can build your VB6 as an ActiveX exe and add a reference to it in your vb.net project. As long as the VB6 types are Public you should be able to use them in vb.net. – djv Nov 08 '21 at 19:17
  • It might help to post both declarations of `Options` here. – Craig Nov 08 '21 at 20:02
  • There's one main thing I had to change regarding the structures, and that's due to [VB.net not allowing array structure members to be declared with an initial size](https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/error-messages/bc31043). Options has it's members' size declared when the program is initialized (on load of main form). That happens before FileGet() tries to populate Options, would this, or using arrays for structure members at all, be a problem? It seems like arrays on 64-bit systems are larger in VB.net than arrays in VB6. – VeryColdAir Nov 08 '21 at 20:32
  • @Craig Added the declarations. – VeryColdAir Nov 08 '21 at 20:48
  • Thanks for the update, I suspect that the issue may be related to the array members. There are flags to use with things like that for use with native interop, but I don't know if they would help with file operations. – Craig Nov 08 '21 at 20:55
  • (Only) If it comes to it and you really need to understand the byte-level structure of the files you should open them in a hex editor. This may seem tedious at first but will ultimately give you the clearest understanding of the file format. Also documentation / resources like https://www.codeguru.com/vb/gen/vb_misc/algorithms/article.php/c7495/How-Visual-Basic-6-Stores-Data.htm can be essential. – StayOnTarget Nov 09 '21 at 13:14
  • Question for OP - is it a requirement to replace all the VB6 code? Could you accept some VB6 DLLs as a continuing part of the project? – StayOnTarget Nov 09 '21 at 13:15
  • @StayOnTarget I could accept some VB6 DLLs, I was thinking about that after reading djv's comments. – VeryColdAir Nov 09 '21 at 14:23
  • @StayOnTarget I opened the file in a hex editor, and there's 96124 (0001777C) values. The file size is also 94KB. If each record is suppose to be 32,000 bytes and there's 4 records, there should be 128,000bytes or 128,000/1024 = 125KB. From what I can tell, the last record is getting truncated. The first three records have hex values followed by a ton of "00" until the next multiple of 32,000th record. Starting at the 96,000th spot, we see more hex values, but then the file seemingly abruptly ends. – VeryColdAir Nov 09 '21 at 14:28
  • You might want to try the `VBFixedArray` attribute for your structure members. Documentation for the attribute shows it being used with the legacy compatibility file I/O. – Craig Nov 09 '21 at 16:26
  • Based on the docs, I think it's not surprising that the file isn't padded past the end of the last record. Most of the null data you see in the file is padding from one record to another. The MSDN docs in Microsoft.VisualBasic namespace, FileSystem class should have a fair amount of info on the file format. – Craig Nov 09 '21 at 16:31
  • @Craig Changing my structure array members to include the attribute (`Public mem04 () As Single` to ` Public mem04 () As Single`) somehow prevents the unable to read beyond the end of the stream exception. Everything is able to be read correctly. Thanks for your help. – VeryColdAir Nov 09 '21 at 20:46

2 Answers2

2

I believe the issue is that you have your structure declared with dynamic arrays, but the VB6 structure is declared with fixed-length arrays (which the other answer at the time of this writing also notes). The legacy binary file processing expects a variable-length array in your structure to correspond to an array descriptor in the binary file, so it's perhaps not surprising that trying to read a binary floating point value as an array descriptor leads to problems.

I think the best approach for this is to use the VBFixedArray attribute on your array members. The legacy binary file processing understands this attribute, and it should then read the file correctly. (There is also a VBFixedString attribute for describing fixed-length strings from VBA Types.)

The result would be something like this:

Public Structure typDesign
    Public mem01 As Single
    Public mem02 As Single
    Public mem03 As Single
    <VBFixedArray(3)>
    Public mem04() As Single
End Structure

You can find the .NET 5 docs for the attribute here: https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualbasic.vbfixedarrayattribute?view=net-5.0

There is also some discussion of the legacy binary file format in the docs for the FileSystem class in the Microsoft.VisualBasic namespace.

Craig
  • 2,248
  • 1
  • 19
  • 23
0

Arrays of the sort you see in these VB6 Types are not SafeArrays. They're actually vectors, and as such can be replaced with a sequence of the underlying variable: x(3) As Single can just be x1 As Single, x2 As Single, etc in Vb.Net. In contrast, x() As Single inside the type is a SafeArray and only occupies the 4 bytes used by the descriptor.

Jim Mack
  • 1,070
  • 1
  • 7
  • 16
  • 1
    Also see the `VBFixedArray` attribute. – Craig Nov 09 '21 at 16:27
  • And the descriptor as written for a dynamic array will be more than four bytes, also see the MSDN docs for `FileSystem.FileGet`. I believe it will be a minimum of ten bytes for a rank-one array. – Craig Nov 09 '21 at 16:38