OK I got sidetracked but did eventually find a solution for my problem. In the end, the key is understanding the data types and how they map between FORTRAN and .NET. If we take my example above then I updated my code to replicate the binary file generated by the FORTRAN program.
I verified by taking both binary files and running them though an F90 binary reader. All data was comparable. That is to say, strings and integers were exact and singles were fairly close. For this process, the single data was within a tenth and that was good enough for me since some of the data is rounded.
Public Class CityStats
Public Property Dept As String
Public Property Weight As Integer
Public Property NumOps As Integer
Public Property LastOpDate As DateTime
Public Property CheckDate As DateTime
Public Property 99Percentile As Double
Public Property 90Percentile As Double
Public Property 50Percentile As Double
End Class
Dim city As String = "BB", xcode as Integer = 1
'open a main BinaryWriter
Dim str As Stream = File.Open"C:\stats.BIN", FileMode.Create)
Using bw As BinaryWriter = New BinaryWriter(str)
'write to a temp BinaryWriter so we can determine the record length before we write it to the main BinaryWriter.
Dim msRec As New MemoryStream
Dim bwRec As New BinaryWriter(msRec)
Dim cx As String = F90FixedString(city & xcode.ToString("00"), 3)
bwRec.Write(cx.ToCharArray) 'see note below about strings vs char arrays
bwRec.Write(cityStats.Count)
'cycle through all the records for one X code and write out to binary. in this case a FORTRAN REAL defaults
'to a 4 byte single precision float or in .NET it is a type Single. Also we do not write out a string as the
'BinaryWriter will prepend it with the string length. instead we convert the string to an array of characters.
'The Fortran Integer is a 4-byte signed integer by default so that is equivalent to the .NET Integer type.
For Each out In CityStats
bwRec.Write(out.Dept.ToCharArray)
bwRec.Write(out.Weight)
bwRec.Write(out.NumOps)
bwRec.Write(out.CheckDate.ToString("ddMMMyy").ToUpper.ToCharArray)
bwRec.Write(CSng(out.99Percentile))
bwRec.Write(CSng(out.90Percentile))
bwRec.Write(CSng(out.50Percentile))
Next out
'get record length in bytes. Note: BaseStream.Length is 64-bit but my data is rather small so am confident I will never get to > 32-bit
Dim recLenInBytes As Integer = CInt(bwRec.BaseStream.Length)
'now write the temp BinaryWriter to our main BinaryWriter. preceded and followed by a 32-bit integer
'containing the record length.
bw.Write(recLenInBytes)
bw.Write(msRec.ToArray)
bw.Write(recLenInBytes)
End Using
'a fixed length string in FORTRAN is left-justified and by default conisist of spaces.
Public Function F90FixedString(ByVal inp As String, ByVal strLen As Integer) As String
'checks
If inp Is Nothing Then Throw New Exception("Null string. Can't create a fixed length string.")
'if the string is already the required length then just return the input string
If inp.Length = strLen Then Return inp
'pad the string to the right with spaces if required
Dim out As String = inp.PadRight(strLen, " "c)
'now check to see if the new string is longer than required and if so then we take the
'proper amount of characters starting from the left.
If out.Length > strLen Then out = out.Substring(0, strLen)
'return the new string
Return out
End Function
Information was a bit tough to find and some of it was outdated. With these links and some trial and error it all worked out. I'm using the Lahey F90 compiler so your results may be different based on your particular compiler and settings.
Opening Binary Files in Fortran: Status, Form, Access
https://software.intel.com/content/www/us/en/develop/documentation/fortran-compiler-developer-guide-and-reference/top/compiler-reference/data-and-i-o/fortran-i-o/record-length.html
Unformatted files (FORM= 'UNFORMATTED'): Specify the record length in 4-byte units, unless you specify the assume byterecl compiler option to request 1-byte units.
https://community.intel.com/t5/Intel-Fortran-Compiler/Convert-REAL-8-Unformatted-Sequential-file/td-p/771973
The Visual Fortran SEQUENTIAL UNFORMATTED file layout is one that is very common on UNIX systems, including DIGITAL UNIX. Each Fortran "record" is preceded and followed
by a 32-bit integer containing the record length (the integer is stored in the "little-endian" layout, with the least-significant bit in the lowest addressed byte.) This record length is required to apply Fortran record semantics, including the ability to use BACKSPACE. Unfortunately, this is not correctly documented in the Programmer's Guide - the layout described there is actually that of Microsoft Fortran PowerStation. This will be corrected in future editions.
Why does BinaryWriter prepend gibberish to the start of a stream? How do you avoid it?
https://software.intel.com/content/www/us/en/develop/documentation/fortran-compiler-developer-guide-and-reference/top/compiler-reference/data-and-i-o/fortran-i-o/record-length.html
.NET BinaryWriter.Write() Method -- Writing Multiple Datatypes Simultaneously
https://learn.microsoft.com/en-us/dotnet/api/system.io.binarywriter.write?view=netframework-4.7