Skip to content

andrevdl/PackCalculator

Repository files navigation

PackCalculator

Model and visualize C / TwinCAT–style struct memory layout — size, alignment, and padding, byte by byte.

.NET License Last commit

A small tool for modeling C / TwinCAT–style memory layout and computing struct sizes, alignment, and padding. You compose objects from primitives, arrays, strings, and nested objects, then render a human-readable memory map with MemoryViewer.DisplayMemoryView.

Features

  • 📐 Accurate layout — computes member offsets, struct size, and alignment the way C / TwinCAT / StructLayout do.
  • 🧱 Composable building blocks — primitives, arrays, STRING/WSTRING, and arbitrarily nested objects.
  • 🎚️ Configurable packing — set a pack value (1–8) to cap alignment, just like #pragma pack / {attribute 'pack_mode'}.
  • 🔍 Readable memory map — renders data, data padding, and trailing object padding as an indented tree.

Concepts

Type Represents Size Alignment
Primitive(size) A scalar field (e.g. BYTE, INT, LREAL) size size
CArray(length, element) A contiguous array length * element.Size element.Alignment
CString(length) STRING(length) length + 1 (null terminator) 1
CString(length, wide: true) WSTRING(length) (length + 1) * 2 2
CObject(name, context, pack) A struct sum of members + padding largest member alignment

Context(defaultPack) sets the maximum alignment (the pack value, 1–8). Each member is aligned to min(member.Alignment, pack). The struct's total size is rounded up to a multiple of its largest member alignment (trailing object padding), and gaps inserted to align members appear as data padding.

Note on the default pack value: In a typical C or TwinCAT application the effective pack value isn't fixed — it commonly defaults to 4 on 32-bit targets and 8 on 64-bit targets, because it follows the natural word size of the OS / hardware (and the compiler / PLC runtime). The same source can therefore lay out differently across platforms unless an explicit pack is set (#pragma pack / {attribute 'pack_mode'}). Pick the pack value here that matches your target to reproduce its layout.

A pack_mode (or member/object pack) of 0 means "natural alignment" — i.e. no override, so each member uses its own natural alignment capped only by the surrounding context's default pack. In this library, passing pack: 0 to a member/object falls back to the Context default pack for that reason.

Project structure

Project Type Purpose
PackCalculator Class library The reusable layout engine — reference this from your own code.
PackCalculator.Example Console app A runnable demo showing how to compose objects and print a memory map.

Integrate by adding a reference to the PackCalculator library:

<ProjectReference Include="..\PackCalculator\PackCalculator.csproj" />

Usage

var context = new Context(8); // pack = 8

var obj = new CObject("Example", context, 0);
obj.AddMember(new Primitive(1));
obj.AddMember(new Primitive(8));

MemoryViewer.DisplayMemoryView(obj);
Console.WriteLine($"Size: {obj.Size} bytes");

Examples

1. Alignment padding

A byte followed by an 8-byte field forces the larger field onto an 8-byte boundary.

var obj = new CObject("Simple", new Context(8), 0);
obj.AddMember(new Primitive(1));
obj.AddMember(new Primitive(8));
MemoryViewer.DisplayMemoryView(obj);
Simple
    Primitive(1):	1 bytes
    Data Padding:	7 bytes
    Primitive(8):	8 bytes
End:	16 bytes

2. Packing (pack = 1)

The same members under Context(1) cap every member's alignment at 1, removing all padding.

var obj = new CObject("Packed", new Context(1), 0);
obj.AddMember(new Primitive(1));
obj.AddMember(new Primitive(8));
MemoryViewer.DisplayMemoryView(obj);
Packed
    Primitive(1):	1 bytes
    Primitive(8):	8 bytes
End:	9 bytes

3. Strings (STRING vs WSTRING)

STRING is byte-aligned; WSTRING is word-aligned (2 bytes/char), so it can introduce a padding byte after an odd-sized field. Both reserve space for a null terminator.

var obj = new CObject("Message", new Context(8), 0);
obj.AddMember(new Primitive(1));            // byte flag
obj.AddMember(new CString(5, wide: true));  // WSTRING(5)
MemoryViewer.DisplayMemoryView(obj);
Message
    Primitive(1):	1 bytes
    Data Padding:	1 bytes
    WSTRING(5):	12 bytes
End:	14 bytes

4. Arrays and nested objects

Arrays are laid out contiguously, and nested objects align by their own largest member.

var inner = new CObject("Inner", new Context(8), 0);
inner.AddMember(new Primitive(3));

var obj = new CObject("Test", new Context(8), 0);
obj.AddMember(new Primitive(1));
obj.AddMember(new Primitive(2));
obj.AddMember(new Primitive(8));
obj.AddMember(new CArray(3, new Primitive(1)));
obj.AddMember(new CString(10));
obj.AddMember(new CString(10, wide: true));
obj.AddMember(inner);
MemoryViewer.DisplayMemoryView(obj);
Test
    Primitive(1):	1 bytes
    Data Padding:	1 bytes
    Primitive(2):	2 bytes
    Data Padding:	4 bytes
    Primitive(8):	8 bytes
    Array[3] of Primitive(1)
        Primitive(1):	1 bytes
        Primitive(1):	1 bytes
        Primitive(1):	1 bytes
    End:	3 bytes
    STRING(10):	11 bytes
    WSTRING(10):	22 bytes
    Data Padding:	2 bytes
    Inner
        Primitive(3):	3 bytes
    End:	3 bytes
    Object Padding:	7 bytes
End:	64 bytes

Releasing

Versioning is handled automatically by MinVer from Git tags (tag prefix v, e.g. tag v1.0.0 → package version 1.0.0). To cut and publish a release:

git tag v1.0.0
git push origin v1.0.0
dotnet pack PackCalculator\PackCalculator.csproj -c Release
dotnet nuget push PackCalculator\bin\Release\PackCalculator.1.0.0.nupkg --api-key <KEY> --source https://api.nuget.org/v3/index.json

Replace v1.0.0 / 1.0.0 with the version you are releasing, and <KEY> with your nuget.org API key. Without a tag, builds produce a 0.0.0-alpha.0.<height> pre-release version.

About

Model and visualize C / TwinCAT–style struct memory layout — size, alignment, and padding.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages