C# StructLayout/FieldOffset和数组索引

5

我在使用FieldOffset与数组时遇到了一些问题。以下代码是一个示例,在这个示例中,它无法正确地工作:

[StructLayout(LayoutKind.Explicit)]
public struct IndexStruct {
    [FieldOffset(0)]
    public byte[] data;

    [FieldOffset(0)]
    public short[] idx16;

    [FieldOffset(0)]
    public int[] idx32;
}

如果我将名为“data”的数组设置为序列化的字节数组,然后尝试使用“idx16”字段检索shorts格式的数据,则索引仍然与byte[]对齐。这意味着idx161会获取数据中的第二个字节,而不是第二个16位字(即第2个和第3个字节)。如果我反过来,索引短整数而不是字节,则偏移对齐方式继承自源数据。我的问题是,有没有一种方法可以解决这个问题?我知道我可以通过乘以元素大小来补偿索引值,但是否还有其他方法? 这里是我在StackOverflow上找到的一个答案,但尝试使用该代码时发现它无法正常工作。使用以下代码在VS中进行单元测试,但没有成功:
[TestMethod()]
public void SumTest() {
    float[] fArr = {2.0f, 0.5f, 0.0f, 1.0f};
    MemoryStream ms = new MemoryStream();
    for (int i = 0; i < fArr.Length; i++) {
        ms.Write(BitConverter.GetBytes(fArr[i]), 0, sizeof(float));
    }
    byte[] buff = ms.ToArray();
    double expected = 3.5f;
    double actual = Sum(buff);
    Assert.AreEqual(expected, actual);
}

提前致谢!

关于您的评论;当然,将索引除以大小可能比不安全的代码更可取 ;-p - Marc Gravell
2个回答

8
问题在于(据我所见),您已经将数组的引用合并了,因此无论哪个数组最后被设置都会获胜。一旦有一个数组,它就使用索引器(而不是字节偏移量)-因此大小并不重要。
正确(或不正确)的方法可能是使用不安全的代码 - 获取数组的指针 - 类似于:
    IndexStruct s = new IndexStruct();
    s.data = new byte[] { 1, 0, 0, 0, 1, 1 };

    unsafe
    {
        fixed (short* data = s.idx16)
        {
            Console.WriteLine(data[0]); // should be 1 (little-endian)
            Console.WriteLine(data[1]); // should be 0
            Console.WriteLine(data[2]); // should be 257
        }
    }

当然,我不确定是否建议这样做 - 但似乎可以达到您想要的目标?

我也在想是否可以完全放弃struct并直接使用不安全的访问方式来访问byte[]

    byte[] raw = new byte[] { 1, 0, 0, 0, 1, 1 };
    unsafe
    {
        fixed (byte* addr = raw)
        {
            short* s = (short*)addr;
            Console.WriteLine(s[0]); // should be 1
            Console.WriteLine(s[1]); // should be 0
            Console.WriteLine(s[2]); // should be 257
        }
    }

是的,那个方法可以行得通,我也考虑过使用它,但如果有一种避免进入不安全代码的方法,我更愿意选择它。但你的方法很好,如果其他方法都失败了,我会采用你的建议。 - Burre
重新解释转换托管引用比使用不安全的代码更加棘手。如果你在做低级别的东西,没有理由避免使用不安全的代码,但完全颠覆CLR类型系统是邪恶的。我认为你的重新解释转换会导致未定义的行为,并可能在将来的CLR版本中出现问题。 - CodesInChaos
1
@CodeInChaos 或者在 WinRT 中,确实如此。 - Marc Gravell

-2

你的FieldOffset定义了结构体中每个数据元素的位置。

将它们全部设置为0,就是告诉编译器它们都在位置0。

我看到的第二件事是你正在创建一个字节、短整型和整型数组。

参见:MSDN StructLayoutAttribute

[StructLayout(LayoutKind.Explicit)]
public struct IndexStruct {
        [FieldOffset(0)]
        public byte[16] data;

        [FieldOffset(16)]
        public short idx16;

        [FieldOffset(18)]
        public int idx32;
}

1
将它们在位置0上叠加是有意为之的,以便让这三个字段指向同一内存区域。我原本期望使用idx16检索字节值时会将字节作为shorts对齐,但似乎并不起作用,我想知道为什么。 - Burre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接