RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 880130
Accepted
Kir_Antipov
Kir_Antipov
Asked:2020-09-11 23:22:57 +0000 UTC2020-09-11 23:22:57 +0000 UTC 2020-09-11 23:22:57 +0000 UTC

类的 PtrToStructure 类比

  • 772

同志们,下面的问题让我很感兴趣:

假设我们有一个为了结构起见的例子,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#我认为根本不可能),如果可以,如何?

即使是最疯狂的想法也受到欢迎!

c#
  • 3 3 个回答
  • 10 Views

3 个回答

  • Voted
  1. Best Answer
    Zergatul
    2020-09-12T00:00:58Z2020-09-12T00:00:58Z

    有几个原因导致这是不可能的。

    Marshal.PtrToStructure<Point>创建结构的副本。例如,如果将结果分配给局部变量,则结构的副本将被压入堆栈。调用此方法后,您可以更改从中复制结构的内存,并且您的副本不会发生任何事情。如您所见,从程序完整性的角度来看,操作本身是安全的。

    Marshal.PtrToClass<MyClass>您很可能是指指针取消引用(类似于 C++)。垃圾收集器可以随时停止整个程序的执行,并在内存中移动对象。可能会发生这样的事情:

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

    Marshal.PtrToStructure被发明来与本机代码交互。.NET-通常意义上的类不能存在于本机代码中,因此它们PtrToClass不存在。

    还有一种简单的使用方式GCHandle,就是禁止内存中的对象移动。

    但在 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 };
    
        // запрещаем перемещение объектов в памяти
        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<MyClass>(ptr1);
        var instance4 = PtrToClass<MyClass>(ptr2);
        Console.WriteLine(instance3.Value);
        Console.WriteLine(instance4.Value);
    
        // разрешаем GC перемещать объекты
        gh1.Free();
        gh2.Free();
    }
    
    • 4
  2. Pavel Mayorov
    2020-09-12T15:01:55Z2020-09-12T15:01:55Z

    您无法将指针转换为对象,原因很简单,即您无法从任何地方获得指向对象的指针:.net 根本没有给您该选项。

    但是如果你真的不需要指向对象的指针,而只需要 IntPtr,你可以使用 GCHandle。

    创建 GCHandle:

    GCHandle.Alloc(obj).ToIntPtr()
    

    删除 GCHandle(如果没有完成 - 将会有内存泄漏!)

    GCHandle.FromIntPtr(ptr).Free()
    

    转换为对象:

    GCHandle.FromIntPtr(ptr).Target
    
    • 3
  3. MSDN.WhiteKnight
    2020-07-19T16:44:09Z2020-07-19T16:44:09Z

    创建指向类的指针 - 不可能(错误 CS0208)

    无法直接创建指向类的指针,因为它由垃圾收集器管理并且可以在内存中重新定位。但是,可以使用 Pinned GCHandle 固定一个类并获得一个指向被固定对象的指针 - 但为此,该类必须仅包含简单类型并具有一个属性[StructLayout(LayoutKind.Sequential)](尽管有名称,它也可以应用于一个类)。

    而且 Marshal.PtrToStructure 也只为结构设计

    严格来说,并非如此。如果类满足相同的条件,Marshal.PtrToStructure 也可以使用:它们只包含简单类型并且具有[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<MyClass>(ptr0);
                my1 = Marshal.PtrToStructure<MyClass>(ptr1);
    
                Console.WriteLine("my0.A=" + my0);
                Console.WriteLine("my1.A=" + my1);
            }
            finally
            {
                //вернем объекты в управление GC
                h0.Free();
                h1.Free();
            }           
    
            Console.ReadLine();
        }
    

    与接受的答案相反,这使用指向类数据本身的指针,而不是对象的标头。此外,调用 Marshal.PtrToStructure 会创建对象的新副本,而不是取消引用指向现有对象的指针。

    • 1

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    是否可以在 C++ 中继承类 <---> 结构?

    • 2 个回答
  • Marko Smith

    这种神经网络架构适合文本分类吗?

    • 1 个回答
  • Marko Smith

    为什么分配的工作方式不同?

    • 3 个回答
  • Marko Smith

    控制台中的光标坐标

    • 1 个回答
  • Marko Smith

    如何在 C++ 中删除类的实例?

    • 4 个回答
  • Marko Smith

    点是否属于线段的问题

    • 2 个回答
  • Marko Smith

    json结构错误

    • 1 个回答
  • Marko Smith

    ServiceWorker 中的“获取”事件

    • 1 个回答
  • Marko Smith

    c ++控制台应用程序exe文件[重复]

    • 1 个回答
  • Marko Smith

    按多列从sql表中选择

    • 1 个回答
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Suvitruf - Andrei Apanasik 什么是空? 2020-08-21 01:48:09 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5