将非托管字节数组划分为;“键入字段”;在.NET4.7中

将非托管字节数组划分为;“键入字段”;在.NET4.7中,.net,interop,unmanaged,.net,Interop,Unmanaged,在.Net中,假设我有一个表示存储页的非托管字节数组。我对每个数组都有一个IntPtr。现在我想将数组划分为“字段” 下面的代码说明了我的意思,但它引发了一个异常,我不知道为什么 [StructLayout(LayoutKind.Explicit, Size = 1024 * 4)] internal unsafe struct Page { // the actual byte array [FieldOffset(0)] public fixed byte buffe

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

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

[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();
}
[StructLayout(LayoutKind.Explicit,Size=1024*4)]
内部不安全结构页
{
//实际字节数组
[字段偏移量(0)]
公共固定字节缓冲区[1024*4];
[FieldOffset(1024*0)]
公共固定字节头[1024];
[FieldOffset(1024*1)]
公共固定字节块1[1024];
[FieldOffset(1024*2)]
公共固定字节块2[1024];
[FieldOffset(1024*3)]
公共固定字节块3[1024];
}
私有不安全无效测试\u单击(对象发送方,事件参数e)
{
IntPtr p=GCHandle.ToIntPtr(GCHandle.Alloc(new Page(),GCHandleType.pinted));
第页*第页=(第页*)第页;
页面->块1[0]=1;//块2[0]=2;//块3[0]=3;//缓冲区[1024]=1
&&页面->缓冲区[1024*2]==2
//&&page->buffer[1024*3]==3
)
{
MessageBox.Show(“OK”);
}
GCHandle.FromIntPtr(p.Free();
}

正确的方法是什么?我试图实现的是将block1等偏移到正确的指针。看我的例子,如果
p
指向4096字节数组,那么
page->header
应该
=p
page->block1
应该
=p+1024
,等等。

GCHandle.ToIntPtr
不是指向
page
对象的指针。它只是一个用整数表示的GC句柄(例如,允许您轻松地将“伪引用”传递给句柄,以便将未更改的代码传递给句柄,然后再返回)。您正在随机写入内存:)

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

另外,对于大多数场景,
GCHandle
是一种过度杀伤力。如果可以包含范围的固定,最好使用
fixed


最后,为了避免评论中出现这种情况:
fixedbyte
只是一个字节指针,而不是一个托管数组。你不会从重叠的领域中获得太多;这相当于只取一个指向数组中某个元素的指针(例如
block2==&buffer[1024*2]
)。在大字节缓冲区的情况下,可能也没有太多额外的危险,不过-我需要仔细检查规范,以检查它是否合法:)

奇怪,它对您有效。重叠是合法的。它经常用于铸造。尽管如此,我试图实现的是将block1等偏移到正确的指针。我发现了错误<代码>页面*页面=(页面*)p应该是
Page*Page=(Page*)&p否,这只是将指针随机移动到一个不会对.NET运行时造成太大破坏的位置。您需要小心使用不安全的代码:)
p
已经是指针。通过执行
&p
,可以将指针指向变量p。这与您的
页面
对象无关!这只意味着您正在写入堆栈空间,这不太可能破坏东西。非常感谢您的帮助!我必须为应用程序的生命周期固定对象。我所需要的是不可能用
fixed
(我通常在要求不太严格的情况下使用)。@IamIC是的,这是不正确的。您想改为执行
(Page*)p
。@IamIC如果您在C语言中遇到同样的问题,您就不会这么快注意到它。这不像C阻止您写入内存中的随机有效地址:D如果您获取
malloc
的结果并执行完全相同的操作,那么它可能不会失败(在DOS中),例如,即使
malloc
返回空指针。如果将指针指向用于存储
malloc
结果的变量,则会出现同样的问题。但实际上,在这种情况下,您可以完全避免托管内存和GC—只需将指针保留在某个位置,就像在C中一样。@IamIC唯一明显更简单的情况是在C代码中使用静态分配的数组。在C#中没有办法做同样的事情,因为实际上没有任何静态分配,也没有全局可变变量。但是如果你在C中做了最接近的事情,并把
Page[]
放在一个字段中,你所需要做的就是
fixed(Page*Page=&pages[123])
得到一个指向124页的指针。你只是走了一条特别多风的弯路,而不是做一件简单的事情:几年前DI写了一些线程间优先级队列。他们比破坏者更快。关键是把直觉扔掉。在许多方面,最有效的方法往往是在不可想象的领域。