x64 C# 应用程序中结构中的 FieldsOffset

FieldsOffset in structs in x64 C# application

我们想在 x64 应用程序中调用 C++ 函数:https://www.inventcom.net/fanuc-focas-library/misc/cnc_diagnoss

我们需要将 ODBDGN 结构传递给函数。这个结构中有一个联合,所以在 c# 中我们不能定义联合,我们需要这样做:

[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;
    }

在 32 位应用程序中,初始化此结构工作正常。

Int 64 位应用程序,初始化此结构不起作用并抛出此错误:

    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

我了解在 x64 应用程序中,默认 Pack 值是 8,所以偏移量必须是 8 而不是 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;
    }

但是如果我们调用 cnc_diagnoss C++ 函数,在结构中我们缺少 typecdatas

之间的 2 个八位字节

但是当我们这样声明结构时:

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

这有效(不是联合,但如果数据是字节)并且 typecdatas

之间没有任何遗漏

在这种情况下,datano 是偏移量 0 type 是偏移量 2,cdatas 是偏移量 4

那么为什么我们不能在那种情况下创建显式布局呢?我不明白这是怎么回事...

这里的问题很常见。您的基本经验法则是,如果您使用 FieldOffset,您将使用 0 的偏移值。换句话说,您复制了 C++ 联合。这使您可以最接近地映射到 C++ 类型的声明方式,并允许 p/invoke 编组器正确对齐您的结构,而无需您手动执行此操作。它确实意味着引入一个额外的类型,但这是值得的。它是这样的:

[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;
}

虽然我个人并不认为这里真的有必要使用联合。另一种选择是像这样声明它:

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

然后向 class 添加辅助方法,允许用户使用其他基本类型的数组获取和设置代码字段。辅助方法将在 short[]byte[] 之间以及 int[]byte[] 之间转换。