【问题标题】:Marshalling structs with non-aligned arrays使用非对齐数组编组结构
【发布时间】:2014-09-17 19:10:07
【问题描述】:

尝试编组此结构时出现异常

[StructLayout(LayoutKind.Explicit, Pack = 1)]
public struct Data
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.U1)]
    [FieldOffset(0x1)]
    public byte[] a2;
}

它说 “无法从程序集 'WTF,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null' 加载类型 'WTF.Data',因为它包含偏移量 1 处的对象字段,该对象字段与非对象字段错误对齐或重叠。”

当我将偏移量 1 更改为 0 或 4 时,一切正常。 我做错了什么?

谢谢

【问题讨论】:

  • 你为什么要把它放在 0x1?
  • 这是一个例子。我需要读取一个字节数组未与 4 对齐的结构
  • 与 C++ DLL 互操作,直接从硬件转发数据结构需要未对齐的数组,但在浏览 SO 时,很难相信大多数 C# 开发人员可能需要它。

标签: c# arrays marshalling memory-alignment


【解决方案1】:

请先查看 Hans Passant 的回答 - 对齐数据是一件好事,CLR 强制执行它是有原因的。不过,如果您出于某种原因确实需要或想要“作弊”,这似乎是可能的:

[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public struct Data
{            
    public byte Dummy;

    [MarshalAs( UnmanagedType.ByValArray, SizeConst = 4,
                ArraySubType = UnmanagedType.U1 )]
    public byte[] a2;
}

unsafe 代码也可以:

[StructLayout( LayoutKind.Explicit, Pack = 1 )]
public unsafe struct Data
{
    [FieldOffset( 1 )]
    public fixed byte a2[4];
}

但同样,这可能不是一个好主意。

编辑:第三种选择是简单地使数组长 5 个字节并使其位于偏移量 0,然后忽略第一个字节。这看起来确实更安全。

【讨论】:

  • 谢谢大家。我需要从进程内存中读取严重对齐的结构。由于我无法记录所有字段,因此我试图使用显式布局。现在我知道这是个坏主意。必须使用 1 字节打包的顺序布局为未知字段创建占位符。但是使用联合不会那么干净
【解决方案2】:

[StructLayout] 影响结构的托管布局和封送布局。在 .NET 中有点怪癖,但创建 blittable 结构在互操作方面是一个相当大的胜利,而且 CLR 不能忽视托管代码始终在完全非托管的操作系统上运行的事实。不必创建结构的副本,而只需能够将指针传递给托管版本是一个非常重要的性能胜利。

您的 [FieldOffset] 值违反了 .NET 内存模型的非常强的保证,对象引用分配始终是原子的。一个昂贵的词,意味着另一个线程永远无法观察到仅部分更新的无效对象引用。原子性需要正确对齐,在 32 位模式下是 4 的倍数,在 64 位模式下是 8 的倍数。如果它们未对齐,则处理器可能需要执行多个内存总线周期以将字节粘合在一起。这很糟糕,当另一个线程也在更新变量时,它会导致撕裂。从旧值中获取部分指针值,从新值中获取部分值。剩下的是一个损坏的指针,它使垃圾收集器崩溃。非常糟糕。

从 C# 的高级角度来看,这些东西晦涩难懂,但提供基本的执行保证非常重要。只要您使用 LayoutKind.Explicit,您就不能让它错位为 1,没有解决方法。所以不要使用它。

【讨论】:

  • 我刚刚注意到,如果我们在开头添加一个虚拟字节并使用[StructLayout(LayoutKind.Sequential, Pack = 1)],它似乎确实可以错位。 Marshal.SizeOf = 5Marshal.OffsetOf(a2) = 1。那我想这不是一件好事吗?
  • @Chris - 也许这是 OP 可以接受的解决方法。真的不知道,他没有解释为什么要使用 LayoutKind.Explicit。您应该将其发布为答案,我只关注“为什么它不起作用”部分。
  • 您对unsafe 替代方案有何看法?我是否认为这是数组实际上不是引用类型/System.Array 的特殊情况?
  • @Chris - 回到基础,我认为 OP 应该停止试图假装计算机从 1 而不是 0 开始计数。他的声明没有任何意义,他得到了“你做错了”例外。有点混淆,微软确实假设了一个有效的尝试来正确地制定错误消息。尝试为毫无意义的代码提供替代方案非常容易,还有更多有意义的事情:)
  • 呵呵,是的,我必须同意你的观点。
猜你喜欢
  • 2014-09-20
  • 1970-01-01
  • 1970-01-01
  • 2013-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-29
相关资源
最近更新 更多