agou
agou

Reputation: 738

What is the best way of reading/writing structured binary data in C#

Like in C we could use structure pointers to read or write structured binary data like file headers etc, is there a similar way to do this in C#?

Upvotes: 5

Views: 5299

Answers (3)

Robert Harvey
Robert Harvey

Reputation: 180908

To read arbitrarily-structured data (a struct) from a binary file, you first need this:

public static T ToStructure<T>(byte[] data)
{
    unsafe
    {
        fixed (byte* p = &data[0])
        {
            return (T)Marshal.PtrToStructure(new IntPtr(p), typeof(T));
        }
    };
}

You can then:

public static T Read<T>(BinaryReader reader) where T: new()
{
    T instance = new T();
    return ToStructure<T>(reader.ReadBytes(Marshal.SizeOf(instance)));
}

To write, convert the struct object to a byte array:

public static byte[] ToByteArray(object obj)
{
    int len = Marshal.SizeOf(obj);
    byte[] arr = new byte[len];
    IntPtr ptr = Marshal.AllocHGlobal(len);
    Marshal.StructureToPtr(obj, ptr, true);
    Marshal.Copy(ptr, arr, 0, len);
    Marshal.FreeHGlobal(ptr);
    return arr;
}

...and then just write the resulting byte array to a file using a BinaryWriter.

Upvotes: 3

Jonathon Reinhart
Jonathon Reinhart

Reputation: 137547

Using BinaryReader and BinaryWriter over a MemoryStream tends to be the best way in my opinion.

Parsing binary data:

byte[] buf = f                     // some data from somewhere
using (var ms = new MemoryStream(buf, false)) {   // Read-only
    var br = new BinaryReader(ms);

    UInt32 len = br.ReadUInt32();
    // ...
}

Generating binary data:

byte[] result;
using (var ms = new MemoryStream()) {   // Expandable
    var bw = new BinaryWriter(ms);

    UInt32 len = 0x1337;
    bw.Write(len);
    // ...

    result = ms.GetBuffer();   // Get the underlying byte array you've created.
}

They allow you to read and write all of the primitive types you'd need for most file headers, etc. such as (U)Int16, 32, 64, Single, Double, as well as byte, char and arrays of those.  There is direct support for strings, however only if 

The string is prefixed with the length, encoded as an integer seven bits at a time.

This only seems useful to me if you wrote the string in this way from BinaryWriter in this way. It's easy enough however, say your string is prefixed by a DWord length, followed by that many ASCII characters:

int len = (int)br.ReadUInt32();
string s = Encoding.ASCII.GetString(br.ReadBytes(len));

Note that I do not have the BinaryReader and BinaryWriter objects wrapped in a using() block. This is because, although they are IDisposable, all their Dispose() does is call Dispose() on the underlying stream (in these examples, the MemoryStream).

Since all the BinaryReader/BinaryWriter are is a set of Read()/Write() wrappers around the underlying streams, I don't see why they're IDisposable anyway. It's just confusing when you try to do The Right Thing and call Dispose() on all your IDisposables, and suddenly your stream is disposed.

Upvotes: 9

Bishnu Paudel
Bishnu Paudel

Reputation: 2079

Here is an simple example showing how to read and write data in Binary format to and from a file.

using System;
using System.IO;
namespace myFileRead
{
    class Program
     {
       static void Main(string[] args)
       {
           // Let's create new data file.
           string myFileName = @"C:\Integers.dat";
            //check if already exists
            if (File.Exists(myFileName))
               {
                Console.WriteLine(myFileName + " already exists in the selected directory.");

                return;
            }

            FileStream fs = new FileStream(myFileName, FileMode.CreateNew);

            // Instantialte a Binary writer to write data

            BinaryWriter bw = new BinaryWriter(fs);

            // write some data with bw

            for (int i = 0; i < 100; i++)

            {
                    bw.Write((int)i);
            }

            bw.Close();
            fs.Close();

            // Instantiate a reader to read content from file
            fs = new FileStream(myFileName, FileMode.Open, FileAccess.Read);

            BinaryReader br = new BinaryReader(fs);

            // Read data from the file

            for (int i = 0; i < 100; i++)
            {
                //read data as Int32 
                Console.WriteLine(br.ReadInt32());
            }
            //close the file 
            br.Close();
            fs.Close();           
       }

    }

}

Upvotes: 0

Related Questions