28

Want to do this: (EDIT: bad sample code, ignore and skip below)

struct RECORD {
    char[] name = new char[16];
    int dt1;
}
struct BLOCK {
    char[] version = new char[4];
    int  field1;
    int  field2;
    RECORD[] records = new RECORD[15];
    char[] filler1 = new char[24];
}

But being unable to declare array sizes in struct, how do I reconfigure this?

EDIT: The reason for the layout is I'm using BinaryReader to read a file written with C structs. Using BinaryReader, and a C# struct union (FieldOffset(0)), I'm wanting to load the header as a byte array, then read it as it was intended originally.

[StructLayout(LayoutKind.Sequential)]
unsafe struct headerLayout
{
    [FieldOffset(0)]
    char[] version = new char[4];
    int fileOsn;
    int fileDsn;
    // and other fields, some with arrays of simple types
}

[StructLayout(LayoutKind.Explicit)]
struct headerUnion                  // 2048 bytes in header
{
    [FieldOffset(0)]
    public byte[] headerBytes;      // for BinaryReader
    [FieldOffset(0)]
    public headerLayout header;     // for field recognition
}
Robert Kerr
  • 1,291
  • 2
  • 17
  • 34

5 Answers5

30

Use fixed size buffers:

[StructLayout(LayoutKind.Explicit)]
unsafe struct headerUnion                  // 2048 bytes in header
{
    [FieldOffset(0)]
    public fixed byte headerBytes[2048];      
    [FieldOffset(0)]
    public headerLayout header; 
}

Alternativ you can just use the struct and read it with the following extension method:

private static T ReadStruct<T>(this BinaryReader reader)
        where T : struct
{
    Byte[] buffer = new Byte[Marshal.SizeOf(typeof(T))];
    reader.Read(buffer, 0, buffer.Length);
    GCHandle handle = default(GCHandle);
    try
    {
        handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
    }
    finally
    {
        if (handle.IsAllocated) 
            handle.Free();
    }
}
Felix K.
  • 6,201
  • 2
  • 38
  • 71
  • The fixed sized buffer is working for simple types, but one of my block reads contains an array of 22 records of 92 bytes each. The record contains 18 fields. "Fixed size buffer type must be one of the following: bool, byte, short, int, long, char, sbyte, ushort, uint, ulong, float or double" But I am much closer to entire solution thanks to fixed. – Robert Kerr Jan 02 '12 at 19:53
  • Felix, but if i have struct inside struct, how can i get inner struct if i use your generic `ReadStruct`? – Konstantin Nov 25 '13 at 17:51
  • 1
    @KonstantinK If it's just a plain struct in another struct the code above should work. `Marshal.SizeOf(typeof(T))` calculates the size of the complete struct, including all structs inside. – Felix K. Nov 25 '13 at 23:01
  • Thank you, Felix! I'll try do to it! – Konstantin Nov 26 '13 at 04:08
  • Unfortunally it does not work...I have created separate topic http://stackoverflow.com/questions/20246459/marshalling-nested-structs-to-c-sharp. And tommorow i will open bounty... Perhaps you can suggest any solutions... – Konstantin Nov 28 '13 at 07:10
  • They do break memory-safety though and need an unsafe context like pointers. – John Aug 26 '16 at 08:42
9

Unmanaged structures can contain embedded arrays. By default, these embedded array fields are marshaled as a SAFEARRAY. In the following example, s1 is an embedded array that is allocated directly within the structure itself.

Unmanaged representation
struct MyStruct {
    short s1[128];
}

Arrays can be marshaled as UnmanagedType.ByValArray, which requires you to set the MarshalAsAttribute.SizeConst field. The size can be set only as a constant. The following code shows the corresponding managed definition of MyStruct. C#VB

[StructLayout(LayoutKind.Sequential)]
public struct MyStruct {
   [MarshalAs(UnmanagedType.ByValArray, SizeConst=128)] public short[] s1;
}
Engineer
  • 8,529
  • 7
  • 65
  • 105
David Yachnis
  • 484
  • 5
  • 14
  • I notice using `Marshal.SizeOf` that this struct does indeed wrap `sizeof(short) * 128` bytes. But where is this memory? I am trying to do this without pulling an unmanaged array. I just want to use the memory that is allocated in your 2nd example, but when I try to access it, the array is claimed to be a null ref. – Engineer Apr 12 '18 at 20:04
4

I wouldn't use that pattern in the first place. This kind of memory mapping may be appropriate in c, but not in a high level language like C#.

I'd just write a call to the binary reader for each member I want to read. This means you can use classes and write them in a clean high level way.

It also takes care of endian issues. Whereas memory mapping will break when used on different endian systems.

Related question: Casting a byte array to a managed structure


So your code would look similar to the following (add access modifiers etc.):

class Record
{
    char[] name;
    int dt1;
}
class Block {
    char[] version;
    int  field1;
    int  field2;
    RECORD[] records;
    char[] filler1;
}

class MyReader
{
    BinaryReader Reader;

    Block ReadBlock()
    {
        Block block=new Block();
        block.version=Reader.ReadChars(4);
        block.field1=Reader.ReadInt32();
        block.field2=Reader.ReadInt32();
        block.records=new Record[15];
        for(int i=0;i<block.records.Length;i++)
            block.records[i]=ReadRecord();
        block.filler1=Reader.ReadChars(24);
        return block;
    }

    Record ReadRecord()
    {
        ...
    }

    public MyReader(BinaryReader reader)
    {
        Reader=reader;
    }
}
Community
  • 1
  • 1
CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
  • That would seem simpler if I weren't reading an array of an unknown number of 2K blocks containing an array of 22 structs containing 18 fields. Even with just 1000 records, that's 18000 lines of code to read each field in the records. Maybe I misunderstood your meaning. – Robert Kerr Jan 02 '12 at 20:24
  • You wouldn't have 18000 lines of code for 1000 records. You'd have a loop that processes the 1000 records with 18 lines code code in the body of the loop. – Mike W Jan 02 '12 at 20:26
  • You have 1-2 lines of code per field. But you don't need layout attributes, so the code wouldn't be much longer. But it would be verifiable, safe, more portable and much cleaner. When reading an array you obviously use a loop. – CodesInChaos Jan 02 '12 at 20:36
  • I like your name. It's certainly how I feel sometimes. And your solution, so much more direct than what I was attempting. – Robert Kerr Jan 02 '12 at 20:46
2

Using unsafe code and fixed size buffer this can be done: http://msdn.microsoft.com/en-us/library/zycewsya.aspx

Fixed size buffers are inline-bytes of the struct. They don't live inside of a separate array like your char[] does.

usr
  • 168,620
  • 35
  • 240
  • 369
-2

Unless you really need a struct, you can do this with a class. A class is basically a struct, and will be used exactly the same way, but it can contain methods inside. One of these methods is the constructor, which will initialize default values inside it once you create a new instance with "new". To create a constructor, put a method with the same name of the class inside it. It may receive arguments if you wish.

class RECORD 
{  
public int dt1;
public char[] name; 
public RECORD => name = new char[16] // if it is one-line the {} can be =>
}

class BLOCK 
    {
    public char[] version;
    public int  field1;
    public int  field2;
    public RECORD[] records;
    public char[] filler1;
    public BLOCK() 
        {
        records = new RECORD[15];
        filler1 = new char[24];
        version = new char[4];
        }      
    }

This way when you create a new item of type BLOCK, it will be pre-initialized:

var myblock = new BLOCK(); 
Console.WriteLine(myblock.records.Length); // returns 15
Console.WriteLine(myblock.records[0].Length); // returns 16
Console.WriteLine(myblock.filler1.Length); // returns 24
Zuabros
  • 252
  • 1
  • 5