FieldsOffset in structs in x64 C# application

0

We want to call a C++ function in a x64 application: https://www.inventcom.net/fanuc-focas-library/misc/cnc_diagnoss

We need to pass a ODBDGN struct to the function. This struct has an union in it so in c# we cannot defined union we need to do this:

[StructLayout(LayoutKind.Explicit, Pack=4)]
    public class ODBDGN
    {
        [FieldOffset(0)]
        public short datano;    /* data number */
        [FieldOffset(2)]
        public short type;      /* axis number */
        [FieldOffset(4),
        MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_AXIS)]
        public byte[] cdatas;
        [FieldOffset(4),
        MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_AXIS)]
        public short[] idatas;
        [FieldOffset(4),
        MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_AXIS)]
        public int[] ldatas;
    }

In 32bits application, initializing this struct works fine.

Int 64bits application, initializing this struct doesn't work and throw this error:

    System.TypeLoadException
  HResult=0x80131522
  Message=Impossible de charger le type 'ODBDGN' à partir de l'assembly 'CommunicationFanuc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null', car il contient un champ objet à l'offset '4' qui n'est pas correctement aligné ou qui est chevauché par un champ non objet.
  Source=CommunicationFanuc
  StackTrace:
   at CommunicationFanuc.DiagnosticAddress.Read() in D:\Projets\PMM2.0\CommunicationFanuc\Addresses\Channel\Diagnostic\DiagnosticAddress.cs:line 98
   at CommunicationFanuc.Tests.PtmFocasEthernet.ReadDiagnostic() in D:\Projets\PMM2.0\CommunicationFanuc.Tests\Ethernet\PtmFocasEthernet.cs:line 809

I understand in x64 application, default Pack value is 8 so offset must be 8 not 4 :

[StructLayout(LayoutKind.Explicit)]
    public class ODBDGN
    {
        [FieldOffset(0)]
        public short datano;    /* data number */
        [FieldOffset(2)]
        public short type;      /* axis number */
        [FieldOffset(8),
        MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_AXIS)]
        public byte[] cdatas;
        [FieldOffset(8),
        MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_AXIS)]
        public short[] idatas;
        [FieldOffset(8),
        MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_AXIS)]
        public int[] ldatas;
    }

But if we call the cnc_diagnoss C++ function, in the struct we are missing the 2 octet beetween type and cdatas

But when we declare the struct like this:

[StructLayout(LayoutKind.Sequential)]
        public class ODBDGN_Byte
        {
            public short datano;
            public short type;     
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_AXIS)]
            public byte[] cdatas;
        }

this works (not the union but if the data is byte) and there nothing missing between type and cdatas

In this case, datano is offset 0 type is offet 2 and cdatas is offset 4

So why can we not create explicit layout in that case? I don't understand how this works...

c#
struct
64-bit
dllimport
asked on Stack Overflow Oct 25, 2018 by jaroost • edited Oct 25, 2018 by jaroost

1 Answer

1

The problem here is a common one. Your basic rule of thumb is that if ever you use FieldOffset you do so with an offset value of 0. In other words you replicate the C++ union. This allows you to map most closely to the way the C++ types are declared and allows the p/invoke marsaller to align your structures properly, without you having to do so manually. It does mean introducing an extra type, but it is worth it. It goes like this:

[StructLayout(LayoutKind.Explicit)]
public struct ODBDGN_CODE
{
    [FieldOffset(0), MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_AXIS)]
    public byte[] cdatas;
    [FieldOffset(0), MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_AXIS)]
    public short[] idatas;
    [FieldOffset(0), MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_AXIS)]
    public int[] ldatas;
}

[StructLayout(LayoutKind.Sequential)]
public class ODBDGN
{
    public short datano;    
    public short type;      
    public ODBDGN_CODE code;
}

Personally though I don't think that using a union is really necessary here. Another option is to declare it like this:

[StructLayout(LayoutKind.Sequential)]
public class ODBDGN
{
    public short datano;    
    public short type;      
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_AXIS)]
    public byte[] code;
}

And then add helper methods to the class that allowed the user to get and set the code field with arrays of other base types. The helper methods would convert between short[] and byte[], and between int[] and byte[].

answered on Stack Overflow Oct 25, 2018 by David Heffernan • edited Oct 25, 2018 by David Heffernan

User contributions licensed under CC BY-SA 3.0