C#, read structures from binary file

2019-03-28 18:19发布

问题:

I want to read structures from binary. In C++ I would do it like this:

stream.read((char*)&someStruct, sizeof(someStruct));

Is there a similar way in C#? The BinaryReader only works for built-in types. In .NET 4 there is a MemoryMappedViewAccessor. It provides methods like Read<T> which seems to be what I want, except that I manually have to keep track of where in the file I want to read. Is there a better way?

回答1:

It's possible to do something similar in C#, but then you would have to apply a lot of attributes to a structure so that you control exactly how it's laid out in memory. By default the JIT compiler controls how structure members are laid out in memory, which usually means that they are rearranged and padded for the most efficient layout considering speed and memory usage.

The simplest way is usually to use the BinaryReader to read the separate members of the structure in the file, and put the values in properties in a class, i.e. manually deserialise the data into a class instance.

Normally it's reading the file that is the bottle neck in this operation, so the small overhead of reading the separate members doesn't affect the performance noticeably.



回答2:

public static class StreamExtensions
{
    public static T ReadStruct<T>(this Stream stream) where T : struct
    {
        var sz = Marshal.SizeOf(typeof(T));
        var buffer = new byte[sz];
        stream.Read(buffer, 0, sz);
        var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        var structure = (T) Marshal.PtrToStructure(
            pinnedBuffer.AddrOfPinnedObject(), typeof(T));
        pinnedBuffer.Free();
        return structure;
    }
}

You need to ensure your struct is declared with [StructLayout] and possibly [FieldOffset] annotations to match the binary layout in the file

EDIT:

Usage:

SomeStruct s = stream.ReadStruct<SomeStruct>();


回答3:

Here is a slightly modified version of Jesper's code:

public static T? ReadStructure<T>(this Stream stream) where T : struct
{
    if (stream == null)
        return null;

    int size = Marshal.SizeOf(typeof(T));
    byte[] bytes = new byte[size];
    if (stream.Read(bytes, 0, size) != size) // can't build this structure!
        return null;

    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    try
    {
        return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
    }
    finally
    {
        handle.Free();
    }
}

It handles EOF cases successfully as it returns a nullable type.



回答4:

There is no similar way in C#. Moreover, this is deprecated way of serializing due to its non-portability. Use http://www.codeproject.com/KB/cs/objserial.aspx instead.



回答5:

Just to elaborate on Guffa's and jesperll's answer, here a sample on reading in the file header for a ASF (WMV/WMA) file using basically the same ReadStruct method (just not as extension method)

MemoryStream ms = new MemoryStream(headerData);
AsfFileHeader asfFileHeader = ReadStruct<AsfFileHeader>(ms);


[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
internal struct AsfFileHeader
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] 
    public byte[] object_id;
    public UInt64 object_size;
    public UInt32 header_object_count;
    public byte r1;
    public byte r2;
}