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
37 stars 15 forks source link

order of fields in struct has influence #15

Closed drvic10k closed 2 years ago

drvic10k commented 2 years ago

The order in which the fields are defined has influence on the values

Steps too reproduce: take the sample and move the bool a field in the MyStruct down (keep the attribute value)

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

        [FieldOffset(1)]
        public short b;

        [FieldOffset(3)]
        public int c;

        [FieldOffset(0)]
        public bool a;
    }

now when getting the MyStruct value from server, you get different values

_symbolValues.Add(this.Symbols["Globals.myStruct1"], new MyStruct(true,42,99));

image

_symbolValues.Add(this.Symbols["Main.myStruct1"], new MyStruct(true, 260, 300));

image

drvic10k commented 2 years ago

interestingly, this problem disappears, when the bool is changed to byte

but byte is still interpreted as bool (not sure, but this might be the problem on the powershell script side)


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

        [FieldOffset(1)]
        public short b;

        [FieldOffset(3)]
        public int c;

        [FieldOffset(0)]
        public byte a;
    }

_symbolValues.Add(this.Symbols["Main.myStruct1"], new MyStruct(1, 260, 300));

image

RalfHeitmann commented 2 years ago

There is a second place where the order of the fields have to be changed. In the symbolic definition of the MyStruct datatype. Look for the "AddMember" statements in the demo server code. Beneath of that, your FieldOffset statements are now wrong. You have to align the data properly in memory.

drvic10k commented 2 years ago

another test case:

make the struct definition match the order of fields:

            StructType dtStruct = new StructType("MYSTRUCT", null)
                // Add Members
                .AddAligned(new Member("b", dtInt))
                .AddAligned(new Member("c", dtDInt))
                .AddAligned(new Member("a", dtBool))
                ;

image

that means, that these 3 things need to be in sync:

  1. order in the struct definition
  2. attribute value
  3. AddAligned calls order

I expected the numbers 2 and 3 to need to match, but number 1 seems strange

is that really the case?

RalfHeitmann commented 2 years ago
StructType dtStruct = new StructType("MYSTRUCT", null)
                // Add Members
                .AddAligned(new Member("b", dtInt)) // Field-Offset 0
                .AddAligned(new Member("c", dtDInt)) // Field-Offset 2
                .AddAligned(new Member("a", dtBool)) // Field-Offset 6
                ;

If you order your type like that you have (because the FieldOffset is calculated by AddAligned internally);

(Field)Offset 0: b (2 Bytes)
(Field)Offset 2: c (4 Bytes)
(Field)Offset 6: a (1 Byte)

Without having tested it yet, in my opinion the Default-Marshaler should work with both cases below:

[FieldOffset(0)]
public short b;

[FieldOffset(2)]
public int c;

[FieldOffset(6)]
public bool a;

or

[FieldOffset(6)]
public bool a;

[FieldOffset(0)]
public short b;

[FieldOffset(2)]
public int c;

Relevant is only the correct FieldOffset for the Memory alignment - not the order.

drvic10k commented 2 years ago

it works as you describe with byte it fails with bool

RalfHeitmann commented 2 years ago

Seems right. For struct types the .NET Interop Default marshaler is used. That means that the boolean is marshalled with 4-bytes by default, see here:

https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedtype?view=net-6.0 states

The example defines the type 'dtBool' as size '1' (1-Byte). Therefore there is a misalignment in the FieldOffsets. I'll correct the example in forcing the .NET marshaler to use UnmanagedType.U1 for the boolean.

See also: https://docs.microsoft.com/en-us/dotnet/standard/native-interop/customize-struct-marshalling

drvic10k commented 2 years ago

great, I tried that and it works