.net 了解C#中的GCHandle.alloc固定

2cmtqfgy  于 2023-01-14  发布在  .NET
关注(0)|答案(1)|浏览(327)

我无意中发现了一些对我来说没有意义的东西。我的问题在代码注解和下面:

[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
[StructLayout(LayoutKind.Sequential, Size = 4096)]
public unsafe struct BufItems
{
    public fixed byte Buffer[4096];
}

public class Wrapper
{
    public BufItems Items;
    public int Id;
}

private unsafe void button10_Click(object sender, EventArgs e)
{
    Wrapper wrap = new Wrapper();
    wrap.Id = 123;

    fixed(BufItems* ptr = &wrap.Items)
    {
        ptr->Buffer[0] = 99;      // works fine
    }

    // fixed (Wrapper w = wrap)   { /* not possible */ };
    // fixed (Wrapper* w = &wrap) { /* not possible */ };

    // how come I can pin the object this way?
    GCHandle h = GCHandle.Alloc(wrap, GCHandleType.Pinned);

    // what exactly is p pointing to? Wrapper cannot have a pointer.
    IntPtr p = h.AddrOfPinnedObject();
}

我还有一个问题是:我假设字段BufItems Items是作为一个对象创建的(因此是可以固定的),而不是作为wrap类对象示例化的一部分,对吗?否则,固定将不起任何作用,因为wrap可以被GC移动。然而,它是一个结构体,我认为结构体在这种情况下是“嵌入”的。这里实际上发生了什么?

gev0vcfq

gev0vcfq1#

让我们逐行回答您的问题:

fixed (Wrapper w = wrap)   { /* not possible */ };

fixed只允许声明一个指针变量。但是请注意,pinning(fixed语句所做的)在引用类型上是可能的,但不是很有用,因此在C#中没有使用它(编辑:指向引用类型和非blittable类型的指针在C#11中可能是可能的)。

fixed (Wrapper* w = &wrap) { /* not possible */ };
  • Wrapper* 是一个引用类型。允许你获得一个变量的指针,该变量包含对它的引用,反过来又允许你访问对象的实际地址,并把它弄得一团糟。你可以把指针强制转换为object*,然后在变量中存储任何对象,这就破坏了类型安全。
// how come I can pin the object this way?
GCHandle h = GCHandle.Alloc(wrap, GCHandleType.Pinned);

正如我已经说过的,示例固定在.NET中是可能的,但在C#中在语法上是不可能的。你可以用这个方法固定任何blittable类型(没有引用字段)。固定对象保证了它在堆中的位置不会改变。

// what exactly is p pointing to? Wrapper cannot have a pointer.
IntPtr p = h.AddrOfPinnedObject();

也许用下面的代码来说明会更好:

int[] arr = new int[10];
fixed(int* p = arr) { ... }
fixed(int* p = &arr[0]) { ... }

这两行被编译为完全相同的CIL(为了提高性能),但第一行也可以通过使用 GCHandle 来实现,方法与您所使用的相同。AddrOfPinnedObject 返回指向对象中第一个字段的指针,与fixed(BufItems* ptr = &wrap.Items)中的方法相同。

  • BufItems* 是一个值类型,因此 Items 字段不包含引用,而是包含实际的固定数组以及其后的int。

相关问题