- PVSM.RU - https://www.pvsm.ru -
Сразу отвечу на вопрос «а зачем?». Просто интересно получить указатель на объект и потом подумать, что с ним особенного можно сделать :) Ведь если получить указатель, то дальше можно сделать все что угодно. Например, становится возможным изучить поведение SyncBlockIndex, или взять MethodTable и изучив, где что находится, изменить его содержимое. Можно мастерить собственные типы данных, не прибегая к Reflection. В общем можно делать много странных вещей, которые относятся больше к спортивному программированию и к саморазвитию. Однако, приступим.
После проведенной исследовательской работы было выяснено что можно внутрь структуры положить два ссылочных типа и при этом задать их точное выравнивание относительно начала структуры, наложив их друг на друга. Итак, что мы в данном случае имеем?
Для демонстрации выше сказанного был реализована структура `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);
Есть еще один, более удобный способ. Для того чтобы скастить строго типизированный указатель .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 ) // .zV.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 0x00008004
.ver 1:0:0:0
}
.module sample.exe
// MVID: {A224F460-A049-4A03-9E71-80A36DBBBCD3}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // 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<TType> (
!!TType input
) cil managed
{
// Method begins at RVA 0x2254
// Code size 7 (0x7)
.maxstack 2
.locals init (
[0] native int CSS
)
ldarg.0
conv.i4
ldc.i4 4
sub
conv.i
ret
} // end of method Converter::ToPointer
.method public hidebysig static
native int ToPointerWithOffset<TType> (
!!TType input
) cil managed
{
// Method begins at RVA 0x2254
// Code size 7 (0x7)
.maxstack 2
.locals init (
[0] native int CSS
)
ldarg.0
ret
} // end of method Converter::ToPointer
// Methods
.method public hidebysig static
!!TType ToInstance<TType> (
native int input
) cil managed
{
// Method begins at RVA 0x2254
// Code size 7 (0x7)
.maxstack 2
.locals init (
[0] native int CSS
)
ldarg.0
conv.i4
ldc.i4 4
add
conv.i
ret
} // end of method Converter::ToInstance
.method public hidebysig static
!!TType ToInstanceWithOffset<TType> (
native int input
) cil managed
{
// Method begins at RVA 0x2254
// Code size 7 (0x7)
.maxstack 2
.locals init (
[0] native int CSS
)
ldarg.0
ret
} // end of method Converter::ToInstance
} // end of class sandbox.Converter
Этот код по сути содержит класс `System.Runtime.CLR.EntityPtr` с четыремя методами:
А его использование еще проще чем в методе 1:
var pointer = EntityPtr.ToPointer("Hello");
вот и все!
Из проделанной работы можно сделать несколько выводов:
Тем не менее это не отменяет того факта что это крайне интересно и открывает тонны новых возможностей.
Автор: sidristij
Источник [1]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/net/59040
Ссылки в тексте:
[1] Источник: http://habrahabr.ru/post/219619/
Нажмите здесь для печати.