Аналог PtrToStructure для классов

277
21 декабря 2018, 23:10

Товарищи, стал мне интересен следующий вопрос:

Положим, есть у нас примера ради структура System.Drawing.Point
Мы спокойно можем баловаться с ней следующим образом:

// Инициализируем нашу структурку
Point point = new Point(2, 3);
// Получаем ее адрес
Point* pointer = &point;
// Получим ее данные
int x = ((int*)pointer)[0]; // 2
int y = ((int*)pointer)[1]; // 3
// Получаем ту же структуру, разыменовав указатель
Point copied = *pointer;
// Или так (получаем нулевую `Point` по указателю)
copied = pointer[0];
// Или даже так
copied = Marshal.PtrToStructure<Point>(new IntPtr(pointer));

В общем, имея указатель, мы спокойно можем получить структуру в явном виде

Теперь же отойдем от типов значения и перейдем к типам ссылочным, то есть поговорить я хочу о подобных механизмах для классов

Оперируя инстансами классов, мы на деле оперируем их ссылками, то есть, получив указатель, мы получим указатель на ссылку на определенный участок памяти, где как раз и можно найти данные объекта

То есть следующий псевдо-код:

// Обозначим instance'ы классов
MyClass my0 = new MyClass { A = 2 };
MyClass my1 = new MyClass { A = 3 };
// Некоторые действия
IntPtr* ptr0 = ...;
IntPtr* ptr1 = ...;
IntPtr tmp = *ptr0;
ptr0[0] = ptr1[0];
ptr1[0] = tmp;
Console.WriteLine(my0.A); // 3
Console.WriteLine(my1.A); // 2

На деле аналогичен простой смене переменных местами

И вот теперь главный вопрос: каким образом, имея указатель, я могу также, как и в случае со структурой, получить объект?

Проблема в том, что

  • Создание указателя на класс - невозможно (ошибка CS0208)
  • А Marshal.PtrToStructure также рассчитан только на структуры

То есть я ищу нечто такое:

IntPtr* pointer = ...;
// Я знаю, что это не работает, это просто псевдо-код
MyClass my = *((MyClass*)pointer);
my = Marshal.PtrToClass<MyClass>(new IntPtr(pointer));

Собственно, возможно ли такое в C# (не думаю, что прямо совсем никак) и, если да, то как?

Приветствуются даже самые безумные идеи!

Answer 1

Есть несколько причин, почему это невозможно.

Marshal.PtrToStructure<Point> создает копию структуры. Например, если вы присвоите результат в локальную переменную, копия структуры переместится в стек. После вызова этого метода, вы можете изменять память, откуда была скопирована структура, и с вашей копией ничего не случиться. Как видно, сама операция безопасна с точки зрения целостности программы.

Под Marshal.PtrToClass<MyClass> вы скорее всего подразумеваете разыменование указателя (по аналогии с С++). Сборщик мусора может остановить выполнение целой программы в любом месте, и переместить объекты в памяти. Может случиться что-то такое:

IntPtr ptr = получить_указатель_на_экземпляр_класса();
// здесь сборщик мусора остановил потоки, и переместил класс в новое место
MyClass instance = Marshal.PtrToClass<MyClass>(ptr); // ptr указывает непонятно куда

Marshal.PtrToStructure был придуман для взаимодействия с нативным кодом. .NET-классы не могут в обычном понимании существовать в нативном коде, поэтому PtrToClass не существует.

Но на C# можно и такое сделать:

private static unsafe T PtrToClass<T>(IntPtr ptr)
{
    T temp = default(T);
    TypedReference tr = __makeref(temp);
    Marshal.WriteIntPtr(*(IntPtr*)(&tr), ptr);
    T instance = __refvalue(tr, T);
    return instance;
}

Пример использования:

class MyClass
{
    public int Value;
}
static unsafe void Main(string[] args)
{
    var instance1 = new MyClass { Value = 123 };
    var instance2 = new MyClass { Value = 321 };
    TypedReference tr1 = __makeref(instance1);
    TypedReference tr2 = __makeref(instance2);
    IntPtr ptr1 = **(IntPtr**)(&tr1);
    IntPtr ptr2 = **(IntPtr**)(&tr2);
    var instance3 = PtrToClass<MyClass>(ptr1);
    var instance4 = PtrToClass<MyClass>(ptr2);
    Console.WriteLine(instance3.Value);
    Console.WriteLine(instance4.Value);
}
Answer 2

Вы не можете превратить указатель в объект по той простой причине что вам неоткуда взять указатель на объект: .net просто не дает такой возможности.

Но если на самом деле вам не нужен именно указатель на объект, а достаточно лишь IntPtr - можно использовать GCHandle.

Создание GCHandle:

GCHandle.Alloc(obj).ToIntPtr()

Удаление GCHandle (если не сделать - будет утечка памяти!)

GCHandle.FromIntPtr(ptr).Free()

Преобразование в объект:

GCHandle.FromIntPtr(ptr).Target
READ ALSO
Сортировка Dictionary по Key в алфавитном порядке

Сортировка Dictionary по Key в алфавитном порядке

Есть словарь Dictionary<string, int>,как можно отсортировать элементы словаря для вывода в консоль по Key в алфавитном порядке по возрастанию(от "a" до "z") без...

226
Unity - Перемещение UI объектов мышкой

Unity - Перемещение UI объектов мышкой

Как можно реализовать перемещение объектов UI мышкой? Например в инвентаре есть 2 слота, нужно из первого переместить объект во второй слот

318
Объединение пути для .NET 2.0

Объединение пути для .NET 2.0

Каким образом можно правильно описать метод для объединения пути к файлу?

259