Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding GCHandle.Alloc pinning in C#

I inadvertently discovered something that doesn't make sense to me. My questions are in the code comments and below:

[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();
}

One other question I have is this: I assume the field BufItems Items is created as an object (and hence pinnable), rather than being part of the wrap class object instantiation, right? Otherwise pinning would do nothing since wrap could be moved by the GC. However, it's a struct, and I thought structs are "embedded" in such cases. What's actually happening here?

like image 575
IamIC Avatar asked Aug 10 '17 18:08

IamIC


1 Answers

Let's address your questions line-by-line:

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

fixed is only allowed to declare a pointer variable. Note however, that pinning (what the fixed statement does) is possible on reference types, but not so useful, hence there is nothing in C# to use it.

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

Wrapper is a reference type. Allowing you to obtain the pointer to a variable containing the reference to it would in turn allow you to access the actual address of the object, and mess with it horribly. You would be able to cast the pointer to say object* and then store any object in the variable, breaking type safety.

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

As I already said, instance pinning is possible in .NET, but not syntactically in C#. You can pin any blittable type (without reference fields) with this method. Pinning the object guarantees that its location on the heap won't change.

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

Maybe it will be better to illustrate it on this code:

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

The two lines are compiled to exactly same CIL (for performance), but the first line can be also achieved by using GCHandle the same way you did. AddrOfPinnedObject returns the pointer to the first field in the object, the same as in fixed(BufItems* ptr = &wrap.Items).

BufItems is a value type, therefore the Items field does not contain a reference, but the actual fixed array, together with the int following it.

like image 143
IS4 Avatar answered Oct 12 '22 23:10

IS4