C# Read and Write Between Struct and Stream


C# is a managed programming language which means everything by default (or 90% of time), everything is properly managed by the GC (Garbage Collector), you don’t have to worry about freeing resources. If you want to manage a structure in unsafe (non-managed) environment, you will need to use the functions in Marshal class (e.g. StructureToPtr).

Reading Data from Stream and Convert to Struct Type

If we have defined the following structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct TestStruct
{
    [StructLayout(LayoutKind.Explicit)]
    public struct DataInfo
    {
        [FieldOffset(0)] public int X;
        [FieldOffset(0)] public float Y;
    }
 
    public DataInfo Data;
    public double Z;
 }
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct TestStruct
{
    [StructLayout(LayoutKind.Explicit)]
    public struct DataInfo
    {
        [FieldOffset(0)] public int X;
        [FieldOffset(0)] public float Y;
    }

    public DataInfo Data;
    public double Z;
 }

which is equivalent to the C/C++:

1
2
3
4
5
6
7
8
9
#pragma pack(push, 4)
struct {
  union {
     int X,
     float Y
  } Data;
  double Z;
}
#pragma pack(pop)
#pragma pack(push, 4)
struct {
  union {
     int X,
     float Y
  } Data;
  double Z;
}
#pragma pack(pop)

To obtain the size of the struct, we can use Marshal.SizeOf:

1
int size = System.Runtime.InteropServices.Marshal.SizeOf(typeof(TestStruct));
int size = System.Runtime.InteropServices.Marshal.SizeOf(typeof(TestStruct));

To set the values:

1
2
3
4
5
6
var data = new TestStruct()
{
    Data = new TestStruct.DataInfo(),
    Z = 2
};
data.Data.X = 1;
var data = new TestStruct()
{
    Data = new TestStruct.DataInfo(),
    Z = 2
};
data.Data.X = 1;

We can then declare a class that can convert the data in MemoryStream to the specific type of structure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MemoryReader : BinaryReader
{
    public MemoryReader(byte[] buffer)
        : base(new MemoryStream(buffer))
    {
    }
 
    public T ReadStruct<T>()
    {
        var byteLength = Marshal.SizeOf(typeof (T));
        var bytes = ReadBytes(byteLength);
        var pinned = GCHandle.Alloc(bytes, GCHandleType.Pinned);
        var stt = (T) Marshal.PtrToStructure(
            pinned.AddrOfPinnedObject(),
            typeof (T));
        pinned.Free();
        return stt;
    }
}
class MemoryReader : BinaryReader
{
    public MemoryReader(byte[] buffer)
        : base(new MemoryStream(buffer))
    {
    }

    public T ReadStruct<T>()
    {
        var byteLength = Marshal.SizeOf(typeof (T));
        var bytes = ReadBytes(byteLength);
        var pinned = GCHandle.Alloc(bytes, GCHandleType.Pinned);
        var stt = (T) Marshal.PtrToStructure(
            pinned.AddrOfPinnedObject(),
            typeof (T));
        pinned.Free();
        return stt;
    }
}

Store Struct Data in Memory

We then can have a class that inherits from BinaryWriter that has the WriteStruct method which allows you to write the struct data to Memory Stream.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MemoryWriter : BinaryWriter
{
    public MemoryWriter(byte[] buffer)
        : base(new MemoryStream(buffer))
    {
    }
 
    public void WriteStruct<T>(T t)
    {
        var sizeOfT = Marshal.SizeOf(typeof(T));
        var ptr = Marshal.AllocHGlobal(sizeOfT);
        Marshal.StructureToPtr(t, ptr, false);
        var bytes = new byte[sizeOfT];
        Marshal.Copy(ptr, bytes, 0, bytes.Length);
        Marshal.FreeHGlobal(ptr);
        Write(bytes);
    }
}
class MemoryWriter : BinaryWriter
{
    public MemoryWriter(byte[] buffer)
        : base(new MemoryStream(buffer))
    {
    }

    public void WriteStruct<T>(T t)
    {
        var sizeOfT = Marshal.SizeOf(typeof(T));
        var ptr = Marshal.AllocHGlobal(sizeOfT);
        Marshal.StructureToPtr(t, ptr, false);
        var bytes = new byte[sizeOfT];
        Marshal.Copy(ptr, bytes, 0, bytes.Length);
        Marshal.FreeHGlobal(ptr);
        Write(bytes);
    }
}

Example Usage

1
2
3
4
5
6
7
8
9
10
11
12
// writing and save
data.Data.X = 1;
var buffer = new byte[size];
var mem = new MemoryWriter(buffer);
mem.WriteStruct(data);
// read and load
var buffer2 = new byte[size];
var mem2 = new MemoryReader(buffer);
var newdata = mem2.ReadStruct<teststruct>();
// match
Console.WriteLine(newdata.Z);
Console.WriteLine(data.Data.X);
// writing and save
data.Data.X = 1;
var buffer = new byte[size];
var mem = new MemoryWriter(buffer);
mem.WriteStruct(data);
// read and load
var buffer2 = new byte[size];
var mem2 = new MemoryReader(buffer);
var newdata = mem2.ReadStruct<teststruct>();
// match
Console.WriteLine(newdata.Z);
Console.WriteLine(data.Data.X);

C# does not support any type of fixed-length arrays that are embedded in a struct (except in a unsafe context). If you have something like this:

1
2
3
4
5
struct TestStruct
{
    MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)
    BYTE[] UserID;
};
struct TestStruct
{
    MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)
    BYTE[] UserID;
};

The above won’t work and will throw out ArgumentException Error on WriteStruct method:

Error : Type could not be marshaled because the length of an embedded array instance does not match the declared length in the layout

–EOF (The Ultimate Computing & Technology Blog) —

GD Star Rating
loading...
543 words
Last Post: Calculate the Number of Boundary Cubes and Boundary Squares of a NxN Rubik Cube
Next Post: Ping when VPS/Dedicate Server is Restarting

The Permanent URL is: C# Read and Write Between Struct and Stream

Leave a Reply