Ви можете використовувати GCHandleType.Weak замість Pinned. З іншого боку, є інший спосіб отримати вказівник на об’єкт:
object o = new object();
TypedReference tr = __makeref(o);
IntPtr ptr = **(IntPtr**)(&tr);
Потрібен небезпечний блок і є дуже, дуже небезпечним і не повинен використовуватися взагалі. ☺
У той час, коли місцеві провідники не були можливі в C #, існував один недокументований механізм, який міг здійснити подібне - __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 вимагає, щоб тип мав бити, щоб його можна було закріпити. Він має ту перевагу, що не використовує деталі реалізації, недокументовані ключові слова та злом пам'яті.