Table of Contents

Keep a header's defines and size expressions

Advanced · C#. Text and 64-bit defines are published on Constants, #ifdef selects declarations (also through CStructCompilationOptions.Defined), and counts may use %, ?:, sizeof, and offsetof.

Run this example

Prerequisites: the repository's .NET 10 SDK and a checkout of this source. Run from the repository root:

dotnet run --project docs/examples/CStructSharp.Docs.Examples.csproj -c Release -- header-preprocessor

The runner checks a text constant, a 64-bit mask constant, an #ifdef-selected typedef, and payload counts of 5 and 1 from a conditional sizeof/offsetof expression. Success includes PASS header-preprocessor.

Try the related browser lesson.

Complete program

The layout, options, input bytes, helper methods, and required types are all included. To adapt it outside the repository, create a .NET 10 console project, add CStructSharp, and replace Program.cs with this complete file. These examples follow the source version; use a matching package when testing a release.

Download the complete C# source.

// Generated from executable documentation examples. Edit the source region, then regenerate.
using System;
using System.IO;
using System.IO.Pipelines;
using System.Linq;
using System.Buffers;
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using CStructSharp;
using CStructSharp.Codecs;
using CStructSharp.Diagnostics;
using CStructSharp.Introspection;
using CStructSharp.Values;

internal static partial class Program
{
    public static void Main()
    {
        HeaderPreprocessor();
        Console.WriteLine("PASS header-preprocessor");
    }

    private static void HeaderPreprocessor()
    {
        // Preprocessor lines and expression forms that appear in real headers: text and 64-bit defines are
        // published as constants, `#ifdef` selects declarations, and counts may use sizeof, offsetof, % and ?:.
        const string definition = """
            #define MAGIC "CD001"
            #define BLOCK_MASK (1 << 40)
            #define LEGACY_VERSION 1
            #ifdef WIDE_COUNTS
            typedef uint32 count_t;
            #else
            typedef uint16 count_t;
            #endif
            struct header { uint8 kind; uint32 length; };
            struct root {
                count_t count;
                uint8 payload[count % 4 == 0 ? sizeof(header) : offsetof(header, length)];
            };
            """;
        var narrow = new CStruct(definition);
        Equal("CD001", narrow.Constants["MAGIC"].Value);
        Equal(BigInteger.One << 40, narrow.Constants["BLOCK_MASK"].Value);
        Equal(LayoutConstantKind.Integer, narrow.Constants["LEGACY_VERSION"].Kind);
        StructValue aligned = narrow.Parse(new byte[] { 4, 0, 1, 2, 3, 4, 5 }, "root");
        Equal(5, aligned.Get<byte[]>("payload").Length);
        StructValue unaligned = narrow.Parse(new byte[] { 3, 0, 1 }, "root");
        Equal(1, unaligned.Get<byte[]>("payload").Length);

        // The same source compiled with a -D style definition takes the other branch and is its own cache entry.
        var wide = CStruct.GetOrCompile(definition, compilationOptions: new CStructCompilationOptions { Defined = new HashSet<string> { "WIDE_COUNTS" } });
        Equal(4, wide.Layout.Declarations.Single(item => item.Name == "root").Fields[0].Size!.Value);
    }

    private static void Equal<T>(T expected, T actual)
    {
        if (!EqualityComparer<T>.Default.Equals(expected, actual))
        {
            throw new InvalidOperationException($"Expected '{expected}', received '{actual}'.");
        }
    }
}

Try it and diagnose mistakes

Change the count bytes 04 00 to 03 00 and shorten the payload to one byte.

Answer: Count 3 is not a multiple of 4, so the payload has offsetof(header, length) = 1 element. The program contains assertions for its original inputs. When changing an input intentionally, update the expected assertion too; an unchanged assertion is not evidence that the new value is wrong.

Continue with the related guide or choose another recipe.