您可以使用GCHandleType.Weak代替Pinned。另一方面,还有另一种获取指向对象的指针的方法:
object o = new object();
TypedReference tr = __makeref(o);
IntPtr ptr = **(IntPtr**)(&tr);
要求使用不安全的块,非常非常危险,完全不应使用。☺
早在C#中不可能使用by-ref本地对象的时候,就有一种未记录的机制可以完成类似的任务– __makeref。
object o = new object();
ref object r = ref o;
TypedReference tr = __makeref(o);
TypedReference是“泛型”的,这是一个重要的区别;它可以用于存储对任何类型的变量的引用。访问此类引用需要指定其类型,例如__refvalue(tr, object),如果不匹配,则引发异常。
要实现类型检查,TypedReference必须具有两个字段,一个具有变量的实际地址,一个具有指向其类型表示的指针。碰巧的是地址是第一个字段。
因此,__makeref首先用于获取对该变量的引用o。铸造(IntPtr**)(&tr)对待的结构的阵列(经由一个指针表示)IntPtr*(指针到一个通用的指针类型),通过指向它的指针访问。首先取消对指针的引用以获得第一个字段,然后再次对指针进行解引用以获得实际存储在变量中的值o–指向对象本身的指针。
但是,自2012年以来,我想出了一个更好,更安全的解决方案:
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>>));
}
}
这将创建一个动态方法,该方法首先固定对象(因此其存储不会在托管堆中移动),然后执行接收其地址的委托。在执行委托的过程中,该对象仍处于固定状态,因此可以安全地通过指针进行操作:
object o = new object();
ReferenceHelpers.GetPinnedPtr(o, ptr => Console.WriteLine(Marshal.ReadIntPtr(ptr) == typeof(object).TypeHandle.Value));
这是固定对象的最简单方法,因为GCHandle要求该类型必须是可蓝变的才能固定。它的优点是不使用实现细节,未记录的关键字和内存黑客。