Beckhoff / TF6000_ADS_DOTNET_V5_Samples

Sample code for the Version 6.X series of the TwinCAT ADS .NET Packages
https://infosys.beckhoff.com/content/1033/tc3_ads.net/9407515403.html?id=6770980177009971601
BSD Zero Clause License
38 stars 15 forks source link

Wrong size of StructType #24

Closed drvic10k closed 2 years ago

drvic10k commented 2 years ago

When defining a StructType containing fields that are themselves StructTypes the final size of the type is calculated wrongly

Struct fileds need to be aligned on 8byte boudaries, but the size of the parent struct is calclulated as if the fields were aligned without any gaps

Expanding your example witrh the MyStruct:

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode, Pack = 1)]
public class MyComplexStruct
{
    public MyComplexStruct(bool a, short b, int c, MyStruct s)
    {
        this.a = a;
        this.b = b;
        this.c = c;
        this.s = s;
    }

    [FieldOffset(0)]
    [MarshalAs(UnmanagedType.U1)] // Boolean is Marshaled as UnmanagedType.I4 otherwise
    public bool a;

    [FieldOffset(1)]
    // [MarshalAs(UnmanagedType.I2)] (Default)
    public short b;

    [FieldOffset(3)]
    // [MarshalAs(UnmanagedType.I4)] (Default)
    public int c;

    [FieldOffset(8)] // this needs to be 8, with 7 you get an exception
    public MyStruct s;
}

the above mentioned exception: Could not load type 'MyComplexStruct' from assembly because it contains an object field at offset 7 that is incorrectly aligned or overlapped by a non-object field.'

image

Here you can see, that start of the inner struct is correctly aligned at 100F The size of the complex struct though is 14 bytes, but the last field starts at 8 and has size of 7, so the size should be 15

this results in an error, when requesting the value:

image

RalfHeitmann commented 2 years ago

I am not fully aware what is your situatino. You are Implementing the MyComplexStruct in the SymbolicServer Sample code? That seems not to be the latest version.

FieldOffset 7 should be correct - because the SymbolServer with its AlignMember Method actually supports an AlignmentMode=Pack1

Trying to use the same from PLC like here:

attribute 'pack_mode' := '1'} 
TYPE MyComplexStruct : // Size 7 +7 = 14
STRUCT
    a : BOOL; //Offset 0
    b : INT;  //Offset 1
    c : DINT; //Offset 3
    d : MYSTRUCT; // Offset 7
END_STRUCT
END_TYPE

{attribute 'pack_mode' := '1'} 
TYPE MYSTRUCT : // Size 7
STRUCT
    a : BOOL; //Offset 0
    b : INT; // Offset 1
    c : DINT; // Offset 3
END_STRUCT
END_TYPE

I can upload the following symbolic information (TwinCAT 3.1.4024.29, 64-Bit Windows 10):

PS> $s = new-tcsession -port 851 #local PLC

PS> $s | Get-TcDataType MyComplexStruct,Mystruct | fl

Id        : 36
Name      : MyComplexStruct
Namespace : 192.168.2.68.1.1:851
Size      : 14
Category  : Struct
Members   : {BOOL, INT, DINT, MYSTRUCT}

Id        : 37
Name      : MYSTRUCT
Namespace : 192.168.2.68.1.1:851
Size      : 7
Category  : Struct
Members   : {BOOL, INT, DINT}

PS> ($s | Get-TcDataType MyComplexStruct).Members

InstanceName         BitOffset  TypeName             Size       Static
------------         ---------  --------             ----       ------
a                    0          BOOL                 1          False
b                    8          INT                  2          False
c                    24         DINT                 4          False
d                    56         MYSTRUCT             7          False

PS> ($s | Get-TcDataType MyComplexStruct).Members['d'].BitOffset / 8
7

So the FieldOffset is 7 bytes here

PS> ($s | Get-TcDataType MyStruct).Members

InstanceName         BitOffset  TypeName             Size       Static
------------         ---------  --------             ----       ------
a                    0          BOOL                 1          False
b                    8          INT                  2          False
c                    24         DINT                 4          False

Reading values succeeds also:

PS> $s | Read-TcValue -path Main.mycomplexstruct | fl

a       : True
b       : 0
c       : 0
d       : @{a=False; b=0; c=0; PSValue=...}
PSValue : ...

PS> ($s | Read-TcValue -path Main.mycomplexstruct).d | fl

a       : False
b       : 0
c       : 0
PSValue : ...

Double checking now with C# Code (the FieldOffset of 7 is working also):

[StructLayout(LayoutKind.Explicit,Pack =1)]
    public struct MyStruct
    {
        [FieldOffset(0)]
        bool a;
        [FieldOffset(1)]
        short b;
        [FieldOffset(3)]
        int c;
    }

    [StructLayout(LayoutKind.Explicit, Pack = 1)]
    public struct MyComplexStruct
    {
        [FieldOffset(0)]
        bool a;
        [FieldOffset(1)]
        short b;
        [FieldOffset(3)]
        int c;
        [FieldOffset(7)]
        MyStruct s;
    }
using ConsoleApp1;
using System.Runtime.InteropServices;

Console.WriteLine($"MyComplexStruct Size: {Marshal.SizeOf(typeof(MyComplexStruct))}");
Console.WriteLine($"MyStruct Size: {Marshal.SizeOf(typeof(MyStruct))}");
Console.ReadLine();

Console output is:

MyComplexStruct Size: 14
MyStruct Size: 7

Actually I don't see a problem here - I don't know where the exception is coming from. Probably you should explain more detailed or send a test project.

drvic10k commented 2 years ago

just to be sure, I updated to 6.0.155 but it did not work, so here is the minimal (not)working example

With the offset of 8 it runs, but then there is the problem with the struct size

could you reproduce the size problem with the offset producing a gap?

drvic10k commented 2 years ago

sorry, the previous one was not connecting the server, use this one instead

Ads.Simulator.Host.zip

RalfHeitmann commented 2 years ago

The .NET TypeLoad checker really shows a funny behaviour here. You can simply force the TypeLoadException with the following code:

[StructLayout(LayoutKind.Explicit,Pack =1)]
public class MyStruct
{
      [FieldOffset(0)]
      bool a;
      [FieldOffset(1)]
      short b;
      [FieldOffset(3)]
      int c;
}

[StructLayout(LayoutKind.Explicit, Pack = 1)]
public class MyComplexStruct
{
      [FieldOffset(0)]
      bool a;
      [FieldOffset(1)]
      short b;
      [FieldOffset(3)]
      int c;
      [FieldOffset(7)]
      MyStruct s;
}
int sizeOf1 = Marshal.SizeOf(typeof(MyComplexStruct));
int sizeOf2 = Marshal.SizeOf(typeof(MyStruct));

If you change your classes MyStruct and MyComplexStruct from 'class' to 'struct' it will work. It will work also if you remove the FieldOffset attributes and state LayoutKind.Sequential instead of LayoutKind.Explicit in combination with Pack=1. Without understood it fully, my opinion is that this is a bug in the .NET TypeLoader.

Actually important is: MyComplexStruct has size 14 and MyStruct has size 7

If you marshal the MyComplexStruct with size of 15 the internal marshaller will fail with invalid size in line 54:

image

because the SymbolicServer only supports Pack=1 with Sequential Layout (without gaps). We have to extend it to be more flexible in the future and support more PackModes and customizable FieldOffsets in StructType.AddAligned().

Furthermore, I should add an comment in the sample code.

drvic10k commented 2 years ago

excelent, thank you for clarification