在 .Net 4.7 中将非托管字节数组分区为 "typed fields"

Partition an unmanaged byte array into "typed fields" in .Net 4.7

在 .Net 中,假设我有一个表示存储页面的非托管字节数组。每个数组都有一个 IntPtr 。现在我想将数组划分为 "fields".

下面的代码说明了我的意思,但是它抛出了一个异常,我不知道为什么。

[StructLayout(LayoutKind.Explicit, Size = 1024 * 4)]
internal unsafe struct Page
{
    // the actual byte array
    [FieldOffset(0)]
    public fixed byte buffer[1024 * 4];

    [FieldOffset(1024 * 0)]
    public fixed byte header[1024];

    [FieldOffset(1024 * 1)]
    public fixed byte block1[1024];

    [FieldOffset(1024 * 2)]
    public fixed byte block2[1024];

    [FieldOffset(1024 * 3)]
    public fixed byte block3[1024];
}

private unsafe void test_Click(object sender, EventArgs e)
{
    IntPtr p = GCHandle.ToIntPtr(GCHandle.Alloc(new Page(), GCHandleType.Pinned));
    Page* page = (Page*)p;

    page->block1[0] = 1;    // << works
    page->block2[0] = 2;    // << works
    page->block3[0] = 3;    // << System.AccessViolationException

    if (page->buffer[1024] == 1
        && page->buffer[1024*2] == 2 
    //  && page->buffer[1024*3] == 3
    )
    {
        MessageBox.Show("OK");
    }

    GCHandle.FromIntPtr(p).Free();
}

正确的做法是什么?我想要实现的是将 block1 等偏移到正确的指针。所以看看我的例子,如果 p 指向 4,096 字节数组,那么 page->header 应该 = ppage->block1 应该 = p + 1024,等等

GCHandle.ToIntPtr 不是指向 Page 对象的指针。它只是表示为整数的 GC 句柄(例如,允许您轻松地将 "pseudo-reference" 传递给未处理代码的句柄,然后再返回)。您正在随机写入内存:)

您需要使用 GCHandle.AddrOfPinnedObject 来实际获取 Page 对象的地址。

此外,对于大多数情况,GCHandle 是一种矫枉过正。如果您可以包含对范围的固定,最好改用 fixed 块。

最后,只是为了避免在评论中出现:fixed byte 只是一个字节指针,而不是一个托管数组。重叠字段不会给您带来太多好处;它等同于只获取指向数组中元素的指针(例如 block2 == &buffer[1024*2])。在大字节缓冲区的情况下,可能也没有太多额外的危险,不过 - 我需要通过规范来检查它是否合法 :)