79 lines
3.5 KiB
C#
79 lines
3.5 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// <see cref="AbCipStructureMember"/> rows; this helper produces the offset each member
|
|
/// sits at in the parent tag's read buffer.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>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.</para>
|
|
///
|
|
/// <para><see cref="TryBuild"/> returns <c>null</c> on unsupported member types
|
|
/// (<see cref="AbCipDataType.Bool"/>, <see cref="AbCipDataType.String"/>,
|
|
/// <see cref="AbCipDataType.Structure"/>). 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
|
|
/// <see cref="AbCipUdtShape"/> that carries real offsets for BOOL + nested structs; when
|
|
/// that shape is cached the driver can take the richer path instead.</para>
|
|
/// </remarks>
|
|
public static class AbCipUdtMemberLayout
|
|
{
|
|
/// <summary>
|
|
/// Try to compute member offsets for the supplied declared members. Returns <c>null</c>
|
|
/// if any member type is unsupported for declaration-only layout.
|
|
/// </summary>
|
|
public static IReadOnlyDictionary<string, int>? TryBuild(
|
|
IReadOnlyList<AbCipStructureMember> members)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(members);
|
|
if (members.Count == 0) return null;
|
|
|
|
var offsets = new Dictionary<string, int>(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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Natural size + alignment for a Logix atomic type. <c>false</c> for types excluded
|
|
/// from declaration-only grouping (Bool / String / Structure).
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|