Получение указателя на объект .Net
Получение указателя на объект Сразу отвечу на вопрос «а зачем?». А вот просто интересно получить указатель на объект и потом подумать, что с ним особенного можно сделать :) Ведь если получить указатель, то дальше можно сделать все что угодно. Например, становится возможным изучить поведение SyncBlockIndex, или взять MethodTable и изучив, где что находится, изменить его содержимое. Можно мастерить собственные типы данных, не прибегая к Reflection. В общем можно делать много странных вещей, которые относятся больше к спортивному программированию и к саморазвитию. Однако, приступим.Способ №1 После проведенной исследовательской работы было выяснено что можно внутрь структуры положить два ссылочных типа и при этом задать их точное выравнивание относительно начала структуры, наложив их друг на друга. Итак, что мы в данном случае имеем? Любой экземпляр ссылочного типа имеет одиноковый заголовок с системной информацией. Это означает что если мы разместим в нашем классе какое-то поле, и если мы в другом классе также определим единственное поле, они будут располагаться по одному смещению относительно начала каждого из классов Если мы имеем два указателя на экземпляры ссылочных типов в двух полях с единым смещением (`[StructLayout (LayoutKind.Explicit)]` на описании типа структуры и `[FieldOffset (0)]` при описании полей), и при этом записав в один из них указатель на какой-то класс, то обращаясь по этому же указателю (смещение полей едино) по второму полю, тип которого при этом имеет другое наполнение собственными полями, мы интерпретируем эти же данные по-своему. Другими словами, если мы имеем два класса: `A` и `B`, и при этом у `A` есть поле `object _field`, а у класса `B` есть поле `int _field`, то на 32-х разрядной системе имея структуру `C`, в которой по единому смещению находятся поля с типами `A` и `B`, можно записав в `C.A._field = new object ()` считать адрес этого объекта: `var address = C.B._field`. Для демонстрации выше сказанного был реализована структура `SafePtr`, у которой как раз есть два внутренних типа: `ReferenceType` (1) и `IntPtrWrapper` (2) и два свойства для их установки и получения — (5) и (6), которые читают и получают значения полей, выровненных по единому смещению 0 (3) и (4): [StructLayout (LayoutKind.Explicit)] public struct SafePtr { // (1) public class ReferenceType { public object Reference; } // (2) public class IntPtrWrapper { public IntPtr IntPtr; }
// (3) [FieldOffset (0)] private ReferenceType Obj;
// (4) [FieldOffset (0)] private IntPtrWrapper Pointer;
public static SafePtr Create (object obj) { return new SafePtr { Obj = new ReferenceType { Reference = obj } }; }
public static SafePtr Create (IntPtr rIntPtr) { return new SafePtr { Pointer = new IntPtrWrapper { IntPtr = rIntPtr } }; }
// (5) public IntPtr IntPtr { get { return Pointer.IntPtr; } set { Pointer.IntPtr = value; } }
// (6) public Object Object { get { return Obj.Reference; } set { Obj.Reference = value; } }
public void SetPointer (SafePtr another) { Pointer.IntPtr = another.Pointer.IntPtr; } } Ну и напоследок, использование: var safeptr = SafePtr.Create (new object ()); Console.WriteLine («Address of object is: {0}», safeptr.IntPtr.ToInt32()); Вот и все:) Также вы сможете использовать как чистый указатель: var safeptr = SafePtr.Create (new object ()); unsafe { ((int *)safeptr.IntPtr)* = 0; } Console.WriteLine («Address of object is null: {0}», safeptr.Object == null); Способ №2 Есть еще один, более удобный способ. Для того чтобы скастить строго типизированный указатель .Net к обычному указателю, необходимо найти место, где теряется информация о типе. Где становится все равно что хранится. И такое место есть: стек потока. .Net использует обычный стек потока для вызова методов (в общем-то зачем ему использовать что-то еще?), а это значит что если туда помещается указатель на .Net объект, размер которого (например), на 32-х разрядной машине равен 4 байтам, то можно оттуда просто считать 4 байта и все. Главное чтобы JITter не проверял типы при компиляции.C# не позволяет нам записать в стек данные одного типа и достать уже другого. Т.е., другими словами, конструкция:
int point var pointer = new Object (); запрещена. Однако она не запрещена MSIL’ом. На нем и напишем методы конвертации (EntityPtr.il): // Metadata version: v2.0.50215 .assembly extern mscorlib { .publickeytoken = (B7 7A 5C 56 19 34 E0 89) // .z\V. 4… .ver 2:0:0:0 } .assembly EntityPtr
{ .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor (int32) = (01 00 08 00 00 00 00 00) .hash algorithm 0×00008004 .ver 1:0:0:0 }
.module sample.exe // MVID: {A224F460-A049–4A03–9E71–80A36DBBBCD3} .imagebase 0×00400000 .file alignment 0×00000200 .stackreserve 0×00100000 .subsystem 0×0003 // WINDOWS_CUI .corflags 0×00000001 // ILONLY
// =============== CLASS MEMBERS DECLARATION ===================
.class public auto ansi abstract sealed beforefieldinit System.Runtime.CLR.EntityPtr
extends [mscorlib]System.Object
{
.method public hidebysig static
native int ToPointer
ldarg.0 conv.i4 ldc.i4 4 sub conv.i ret } // end of method Converter: ToPointer
.method public hidebysig static
native int ToPointerWithOffset
ldarg.0 ret } // end of method Converter: ToPointer
// Methods
.method public hidebysig static
! TType ToInstance
ldarg.0 conv.i4 ldc.i4 4 add conv.i ret } // end of method Converter: ToInstance
.method public hidebysig static
! TType ToInstanceWithOffset
ldarg.0 ret } // end of method Converter: ToInstance
} // end of class sandbox.Converter
Этот код по сути содержит класс `System.Runtime.CLR.EntityPtr` с четыремя методами: