Arrays, strings, and enums
The layout language has arrays, several kinds of text, enums, and flags. This lesson shows the C# type each one becomes and what the generated reader does with it.
The layout and its bytes
[CStructLayout("""
enum color : uint8 { Red = 1, Green = 2, Blue = 3 };
flag perms : uint8 { Read = 1, Write = 2, Execute = 4 };
struct sample {
uint8 count;
uint16 values[count];
char name[8];
utf8 label[6];
cstring note;
color colour;
perms mode;
};
""", Root = "sample")]
public static partial class Samples
{
}
private static void GeneratedArraysStringsEnums()
{
byte[] bytes =
[
2, 0x34, 0x12, 0x78, 0x56, // count = 2, values = { 0x1234, 0x5678 }
(byte)'p', (byte)'n', (byte)'g', 0, 0, 0, 0, 0, // name[8] = "png" + NULs
0xC3, 0xA9, (byte)'t', (byte)'e', 0, 0, // label[6] = "été" in UTF-8 + NULs
(byte)'o', (byte)'k', 0, // note = "ok" + terminator
2, // colour = Green
7, // mode = Read | Write | Execute
];
Samples.Sample sample = Samples.Parse(bytes);
Equal(2, sample.Values.Length);
Equal((ushort)0x5678, sample.Values[1]);
Equal("png\0\0\0\0\0", sample.Name); // fixed text keeps its NULs unless TrimFixedText is set
Equal("éte\0\0", sample.Label);
Equal("ok", sample.Note);
Equal(Samples.Color.Green, sample.Colour);
Equal(Samples.Perms.Read | Samples.Perms.Write | Samples.Perms.Execute, sample.Mode);
Samples.Sample trimmed = Samples.Parse(bytes, new ReadOptions { TrimFixedText = true });
Equal("png", trimmed.Name);
// A value the enum does not name is kept as the number: the C# enum is a byte underneath.
bytes[^2] = 9;
Equal((Samples.Color)9, Samples.Parse(bytes).Colour);
}
Arrays
uint16 values[count] becomes ushort[] Values. The count is an expression over an earlier member, so the
generated reader evaluates it after reading count - the same expression rules as the runtime, through the
CStructSharp.Generated.Expressions helpers - checks it against ReadOptions.MaxArrayElements, then reads all the
elements with one bulk decode. A fixed array (uint16 values[4]) is the same type with a constant count; a
uint8 data[n] is a byte[]; an array of structs is an array of the generated class; a two-dimensional array
uint8 grid[2][3] is a jagged array byte[][].
Text
| Layout | C# | What the reader does |
|---|---|---|
char name[8] |
string |
Reads 8 one-byte characters. Trailing NULs stay unless ReadOptions.TrimFixedText is set - the bytes are the data. |
wchar name[8] |
string |
Reads 8 UTF-16 code units in the layout's byte order and validates them. |
utf8 label[6] |
string |
A buffer of 6 bytes decoded as UTF-8 (utf16le[N], latin1[N], cp437[N] likewise); the byte count must not exceed MaxStringBytes. |
cstring note |
string |
Reads to the terminator, which is consumed but not part of the value; string and wchar * are the UTF-8 and UTF-16 forms. |
Every rule the runtime applies - the byte limits, an unterminated string, an invalid byte sequence - applies in the generated reader with the runtime's message.
Enums and flags
enum color : uint8 { ... } becomes public enum Color : byte { Red = 1, Green = 2, Blue = 3 } and a flag a
[Flags] enum. The property type is the C# enum, so sample.Colour == Samples.Color.Green compiles and shows up in
IntelliSense.
A stored value the enum does not name is not an error: Samples.Parse(bytes).Colour is (Samples.Color)9, a plain
cast of the number, exactly as C# treats any enum. The runtime API represents the same case as an EnumValueResult
with a null name; the generated class does not need that wrapper because the C# enum already carries the number.
Check yourself
- Why does
"png"come back as"png\0\0\0\0\0"? - What limits how large
values[count]may be? - What is the value of
sample.Colourwhen the byte is9?
Answers
char name[8]is eight bytes of data; the reader returns them unlessTrimFixedTextasks it to drop trailing NULs.ReadOptions.MaxArrayElements(one million by default) and the remaining bytes.(Samples.Color)9: an enum value with no name, printed as9.
Exercise
Change char name[8] to wchar name[4] and adjust the bytes so Name is still "png" followed by one NUL
(in UTF-16 the layout's little-endian order).
Solution
wchar name[4] occupies 8 bytes: 70 00 6E 00 67 00 00 00. The property is still a string; the reader decodes
four UTF-16 code units and, without TrimFixedText, returns "png\0".