namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip; /// /// Computes byte offsets for declared UDT members under Logix natural-alignment rules so /// a single whole-UDT read (task #194) can decode each member from one buffer without /// re-reading per member. Declaration-driven — the caller supplies /// rows; this helper produces the offset each member /// sits at in the parent tag's read buffer. /// /// /// Alignment rules applied per Rockwell "Logix 5000 Data Access" manual + the /// libplctag test fixtures: each member aligns to its natural boundary (SInt 1, Int 2, /// DInt/Real/Dt 4, LInt/ULInt/LReal 8), padding inserted before the member as needed. /// The total size is padded to the alignment of the largest member so arrays-of-UDT also /// work at element stride — though this helper is used only on single instances today. /// /// returns null on unsupported member types /// (, , /// ). Whole-UDT grouping opts out of those groups /// and falls back to the per-tag read path — BOOL members are packed into a hidden host /// byte at the top of the UDT under Logix, so their offset can't be computed from /// declared-member order alone. The CIP Template Object reader produces a /// that carries real offsets for BOOL + nested structs; when /// that shape is cached the driver can take the richer path instead. /// public static class AbCipUdtMemberLayout { /// /// Try to compute member offsets for the supplied declared members. Returns null /// if any member type is unsupported for declaration-only layout. /// public static IReadOnlyDictionary? TryBuild( IReadOnlyList members) { ArgumentNullException.ThrowIfNull(members); if (members.Count == 0) return null; var offsets = new Dictionary(members.Count, StringComparer.OrdinalIgnoreCase); var cursor = 0; foreach (var member in members) { if (!TryGetSizeAlign(member.DataType, out var size, out var align)) return null; if (cursor % align != 0) cursor += align - (cursor % align); offsets[member.Name] = cursor; cursor += size; } return offsets; } /// /// Natural size + alignment for a Logix atomic type. false for types excluded /// from declaration-only grouping (Bool / String / Structure). /// private static bool TryGetSizeAlign(AbCipDataType type, out int size, out int align) { switch (type) { case AbCipDataType.SInt: case AbCipDataType.USInt: size = 1; align = 1; return true; case AbCipDataType.Int: case AbCipDataType.UInt: size = 2; align = 2; return true; case AbCipDataType.DInt: case AbCipDataType.UDInt: case AbCipDataType.Real: case AbCipDataType.Dt: size = 4; align = 4; return true; case AbCipDataType.LInt: case AbCipDataType.ULInt: case AbCipDataType.LReal: size = 8; align = 8; return true; default: size = 0; align = 0; return false; } } }