Страницы

Поиск по вопросам

вторник, 31 декабря 2019 г.

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

#c_sharp #указатели #память


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

Положим, есть у нас примера ради структура 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(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(new IntPtr(pointer));




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

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


Ответы

Ответ 1



Есть несколько причин, почему это невозможно. Marshal.PtrToStructure создает копию структуры. Например, если вы присвоите результат в локальную переменную, копия структуры переместится в стек. После вызова этого метода, вы можете изменять память, откуда была скопирована структура, и с вашей копией ничего не случиться. Как видно, сама операция безопасна с точки зрения целостности программы. Под Marshal.PtrToClass вы скорее всего подразумеваете разыменование указателя (по аналогии с С++). Сборщик мусора может остановить выполнение целой программы в любом месте, и переместить объекты в памяти. Может случиться что-то такое: IntPtr ptr = получить_указатель_на_экземпляр_класса(); // здесь сборщик мусора остановил потоки, и переместил класс в новое место MyClass instance = Marshal.PtrToClass(ptr); // ptr указывает непонятно куда Marshal.PtrToStructure был придуман для взаимодействия с нативным кодом. .NET-классы не могут в обычном понимании существовать в нативном коде, поэтому PtrToClass не существует. Так же есть простой способ при помощи GCHandle, который запрещает перемещение объектов в памяти. Но на C# можно и такое сделать: private static unsafe T PtrToClass(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 }; // запрещаем перемещение объектов в памяти var gh1 = GCHandle.Alloc(instance1, GCHandleType.Pinned); var gh2 = GCHandle.Alloc(instance2, GCHandleType.Pinned); TypedReference tr1 = __makeref(instance1); TypedReference tr2 = __makeref(instance2); IntPtr ptr1 = **(IntPtr**)(&tr1); IntPtr ptr2 = **(IntPtr**)(&tr2); var instance3 = PtrToClass(ptr1); var instance4 = PtrToClass(ptr2); Console.WriteLine(instance3.Value); Console.WriteLine(instance4.Value); // разрешаем GC перемещать объекты gh1.Free(); gh2.Free(); }

Ответ 2



Вы не можете превратить указатель в объект по той простой причине что вам неоткуда взять указатель на объект: .net просто не дает такой возможности. Но если на самом деле вам не нужен именно указатель на объект, а достаточно лишь IntPtr - можно использовать GCHandle. Создание GCHandle: GCHandle.Alloc(obj).ToIntPtr() Удаление GCHandle (если не сделать - будет утечка памяти!) GCHandle.FromIntPtr(ptr).Free() Преобразование в объект: GCHandle.FromIntPtr(ptr).Target

Ответ 3



Создание указателя на класс - невозможно (ошибка CS0208) Создание указателя на класс невозможно напрямую, так как он управляется сборщиком мусора и может быть перемещен в памяти. Однако, класс можно закрепить с помощью Pinned GCHandle и получить указатель на закрепленный объект - но для этого класс должен состоять только из простых типов и иметь атрибут [StructLayout(LayoutKind.Sequential)] (несмотря на название, его можно применить и к классу). А Marshal.PtrToStructure также рассчитан только на структуры Строго говоря, это не так. Marshal.PtrToStructure работает и c классами, если они удовлетворяют тем же условиям: состоят только из простых типов и имеют атрибут [StructLayout(LayoutKind.Sequential)]. Вот пример использования указателей на класс: [StructLayout(LayoutKind.Sequential)] class MyClass { public int A; public override string ToString() { return A.ToString(); } } static void Main(string[] args) { MyClass my0 = new MyClass { A = 2 }; MyClass my1 = new MyClass { A = 3 }; //закрепим объекты в памяти GCHandle h0 = GCHandle.Alloc(my0, GCHandleType.Pinned); GCHandle h1 = GCHandle.Alloc(my1, GCHandleType.Pinned); try { //получим адреса объектов IntPtr ptr0 = h0.AddrOfPinnedObject(); IntPtr ptr1 = h1.AddrOfPinnedObject(); //убедимся, что адреса реальные и по ним можно считать данные (первое поле класса) Console.WriteLine("Значение по ptr0: " + Marshal.ReadInt32(ptr0)); Console.WriteLine("Значение по ptr1: " + Marshal.ReadInt32(ptr1)); IntPtr tmp = ptr0; ptr0 = ptr1; ptr1 = tmp; my0 = Marshal.PtrToStructure(ptr0); my1 = Marshal.PtrToStructure(ptr1); Console.WriteLine("my0.A=" + my0); Console.WriteLine("my1.A=" + my1); } finally { //вернем объекты в управление GC h0.Free(); h1.Free(); } Console.ReadLine(); } В отличие от принятого ответа, здесь используется указатель на сами данные класса, а не на заголовок объекта. Также при вызове Marshal.PtrToStructure создается новая копия объекта, вместо разыменования указателя на существующий объект.

Комментариев нет:

Отправить комментарий