GCHandle: when to use GCHandleType.Normal explicitly?

Reading the Richter J book "Manually Monitoring and Managing the Lifetime of Objects". Jeffrey says there are two ways to manage the lifetime of an object using the GCHandle class:

  • calling the Alloc method using GCHandleType.Normal (GC cannot remove objs, even there may be no reference from the application code)
  • calling the Alloc method with GCHandleType.Pinned (in addition to Normal, GC cannot move such objects)

He says both methods can be used to pass a managed object to unmanaged code. And he tries to explain when developers should call Alloc with GCHandleType.Normal. I don't really understand the explanation about using a normal flag. In both cases, we do not allow the GC to collect objects that have such flags in the GC descriptor table, but in the case of Pinned, we additionally prevent such objects from moving during garbage collection. As I understand it, in the case of normal mode, not a direct address (memory address) is transferred to unmanaged code, but only an index from the GC descriptor table. And that when unmanaged code returns to managed code, that index will be converted to the current / actual address. Restlessness in my head and almost no detailed information on Google and Microsoft, just copy-paste.

My questions:

  • Some root applications (non-weak ones) reference an object on the managed heap and no longer have roots. Does this mean that the corresponding entry in the GC descriptor table will be with GCHandleType.Normal? It seems that no, because Jeffrey says that "GC cannot delete objects even if there are no references from the application code." But if not, what flag will this table record have? Again, MyClass mc = new MyClass (), the corresponding entry for mc in the GC descriptor table has a Normal flag, if not, which one?
  • When (and how please shortcode) do developers really need to use the GCHandleType.Normal flag? It is clearer to me.
+5


source to share


3 answers


If it was not safe to pass an object reference to native code before creating a c descriptor GCHandleType.Normal

, then after creating such a descriptor it will be unsafe because unmanaged code requires a stable pointer. Therefore, the c handle GCHandleType.Normal

does nothing about unmanaged code. I believe this is a documentation error suggesting the opposite.

GCHandleType.Normal

used by managed code to create objects that do not die. For example, some classes Timer

keep instances of themselves so that the timer doesn't stop when you drop the last reference to it.

As I understand it, in the case of normal mode, and not a direct link (memory address) is passed to unmanaged code, but only the index from the GC descriptor table.



This cannot be true, because at the point where the PInvoke occurs, there is not enough information to determine if it is associated GCHandle

with the object you want to pass or not. Mashaler couldn't do it even if he wanted to. Also, what would the unmanaged code do with the descriptor table entry? He doesn't understand this. The descriptor table is an internal CLR environment.

Some root applications (not weak ones) reference an object in the managed heap and there are no more roots. Does this mean that the corresponding entry in the GC descriptor table will be with the GCHandleType.Normal flag? Apparently not, because of Jeffrey says that "the GC cannot delete objects even there may be no, there are no references to the application code." But if not, who will mark this record in the table?

It has the flag you passed in when you created this one GCHandle

. This table has only records for which there is GCHandle

. Normal objects are not tracked.

+4


source


From the documentation for GCHandle :

  Provides a way to access a managed object from unmanaged memory.

If you are going to access an object from unmanaged code, you need to pin the object. In order to get a pinned object, you must ensure that it is marshaled to unmanaged memory.

If you only need an opaque descriptor to pass to unmanaged code so that unmanaged code can pass it again without accessing it, then you don't need the pinned object, but you still need to make sure it's not garbage collected.

Consider this class:

public class MyClass
{
    DateTime dt = DateTime.Now;
}

      

If you try to attach a handle to it like this:

MyClass o = new MyClass();
GCHandle h = GCHandle.Alloc(o, GCHandleType.Pinned);

      

you will get an exception with the message:

Object contains non-primitive or non-blittable data.

This is because the returned handle allows you to get the address of the pinned object. To use this address from unmanaged code, the object must be converted from managed to unmanaged memory.

This code does not throw an exception:



MyClass o = new MyClass();
GCHandle h = GCHandle.Alloc(o, GCHandleType.Normal);

      

Because you cannot use the returned handle to get the address.

So, to answer your questions:

  1. The managed object (MyClass mc = new MyClass ()) has no entry in the GC descriptor table. It will be a garbage collector when not referenced from managed code (I guess that's what Jeffrey Richter calls application code. I haven't read the book).
  2. I use GCHandleType.Normal when I need to pass an opaque handle to unmanaged code .

One scenario is pure C API for managed assembly. The API might look something like this:

MYHANDLE h1 = MyLib_CreateComponent();
MYHANDLE h2 = MyLib_CreateComponent();

MyLib_SetX(h1, 9.81);
double y1 = MYLib_CalcY(h1);
MyLib_SetX(h2, 3.14);
double y2 = MyLib_CalcY(h2);

printf("z = %f\n", y1 + y2);

MyLib_DestroyComponent(h1);
MyLib_DestroyComponent(h2); 

      

There is no direct access to the object from C code.

The C # implementation for the MyLib_CreateComponent () function would look like this:

 public static int CreateComponent()
 {
     MyClass instance = new MyClass();
     GCHandle gch = GCHandle.Alloc(instance, GCHandleType.Normal);
     IntPtr ip = GCHandle.ToIntPtr(h);
     h = ip.ToInt32();
     return h;
 }

      

Inside the managed code, I would create a method to grab the object using a handle:

static MyClass GetObjectFromHandle(int hComp)
{
    IntPtr ip = new IntPtr(hComp);
    GCHandle h = GCHandle.FromIntPtr(ip);
    MyClass comp = h.Target as MyClass;
    return comp;
}

      

+3


source


Typically, you use Normal

it when you expect a pointer to be passed back from native code (which cannot be accessed). And use Pinned

when you expect a pointer from native code to be available .

This is because when you pass an object Normal

to native code, you can only pass IntPtr

to GCHandle

(retrieved via GCHandle.ToIntPt

). And it can only be resurrected back in managed code via GCHandle.FromIntPt

.

A good explanation of how this works can be found here: https://blogs.msdn.microsoft.com/jmstall/2006/10/09/gchandle-tointptr-vs-gchandle-addrofpinnedobject/

+1


source







All Articles