You can use GCHandleType.Weak instead of Pinned. On the other hand, there is another way to get a pointer to an object:
object o = new object();
TypedReference tr = __makeref(o);
IntPtr ptr = **(IntPtr**)(&tr);
Requires unsafe block and is very, very dangerous and should not be used at all. ?
Back in the day when by-ref locals weren't possible in C#, there was one undocumented mechanism that could accomplish a similar thing – __makeref
.
object o = new object();
ref object r = ref o;
//roughly equivalent to
TypedReference tr = __makeref(o);
There is one important difference in that TypedReference is "generic"; it can be used to store a reference to a variable of any type. Accessing such a reference requires to specify its type, e.g. __refvalue(tr, object)
, and if it doesn't match, an exception is thrown.
To implement the type checking, TypedReference must have two fields, one with the actual address to the variable, and one with a pointer to its type representation. It just so happens that the address is the first field.
Therefore, __makeref
is used first to obtain a reference to the variable o
. The cast (IntPtr**)(&tr)
treats the structure as an array (represented via a pointer) of IntPtr*
(pointers to a generic pointer type), accessed via a pointer to it. The pointer is first dereferenced to obtain the first field, then the pointer there is dereferenced again to obtain the value actually stored in the variable o
– the pointer to the object itself.
However, since 2012, I have come up with a better and safer solution:
public static class ReferenceHelpers
{
public static readonly Action<object, Action<IntPtr>> GetPinnedPtr;
static ReferenceHelpers()
{
var dyn = new DynamicMethod("GetPinnedPtr", typeof(void), new[] { typeof(object), typeof(Action<IntPtr>) }, typeof(ReferenceHelpers).Module);
var il = dyn.GetILGenerator();
il.DeclareLocal(typeof(object), true);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Stloc_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Conv_I);
il.Emit(OpCodes.Call, typeof(Action<IntPtr>).GetMethod("Invoke"));
il.Emit(OpCodes.Ret);
GetPinnedPtr = (Action<object, Action<IntPtr>>)dyn.CreateDelegate(typeof(Action<object, Action<IntPtr>>));
}
}
This creates a dynamic method that first pins the object (so its storage doesn't move in the managed heap), then executes a delegate that receives its address. During the execution of the delegate, the object is still pinned and thus safe to be manipulated via the pointer:
object o = new object();
ReferenceHelpers.GetPinnedPtr(o, ptr => Console.WriteLine(Marshal.ReadIntPtr(ptr) == typeof(object).TypeHandle.Value)); //the first pointer in the managed object header in .NET points to its run-time type info
This is the easiest way to pin an object, since GCHandle requires the type to be blittable in order to pin it. It has the advantage of not using implementation details, undocumented keywords and memory hacking.