将结构从装箱复制到分配的内存中?

gen*_*ray 3 c# memory struct

背景

可以说我有一个结构。我们还假装我们不知道它的类型。这就是为什么我们把它装箱而无法拆箱。


public struct Player{
   public float hp;
   public float maxHP;
}

var boxedPlayer = (object)new Player();
var typeSize = typeof(Player);
var ourAllocatedMemoryPTR = (byte*)someAllocCall();

// Copy the object into the new memory
var objHandle = GCHandle.Alloc(boxedPlayer , GCHandleType.Pinned);
var adress = objHandle.AddrOfPinnedObject();
var ptr = adress.ToPointer();
Buffer.MemoryCopy(ptr, ourAllocatedMemoryPTR, typeSize, typeSize);
objHandle.Free();
Run Code Online (Sandbox Code Playgroud)

据我所知,装箱结构...与结构本身的大小不同...因为它被装箱和管理。因此,头部或尾部有一些字节将其定义为一个对象,一个装箱结构。因此上面的示例将整个装箱结构复制到内存中。据我所知。这不是我们想要的。

问题

是否可以仅将框内的结构复制到分配的内存中?我们新分配的内存应该只存储结构,而不是装箱的结构。我认为通过将结构从盒子中取出来可能可以吗?在复制过程中切割将其定义为盒子的部分或头部/尾部?

这可能吗 ?结构体到底是如何装箱的?前后添加了多少字节?记忆中的它是什么样的?

很高兴获得任何帮助!谢谢 :)

Tho*_*ler 6

不要使用内部结构

确切的内部布局可能会发生变化。GCHandle.AddrOfPinnedObject()等方法旨在为您提供指向对象数据的指针,而不是指向标头、方法表或填充字节等内部内容的指针。因此,只需使用这些方法即可,不要自己进行数学计算。

检索固定句柄中对象数据的地址。

强调我的

但无论如何还是很有趣

在撰写本文时(以及几年前),内存中的 .NET 对象具有以下布局:

  • -pointersize:标头
  • 0:方法表(对象的类型)
  • +pointersize:对象数据

您可以通过以下代码看到这一点。我对字段进行了一些简化,以便我们可以比使用float.

public struct Player
{
    public int hp ;
    public int maxHP;
}
class Program
{
    static unsafe void Main()
    {
        var player = new Player();
        player.hp = 0xAABB;
        player.maxHP = 0xCCDD;

        var boxedPlayer = (object) player;
        lock (boxedPlayer)
        {
            Console.ReadLine(); // Put a breakpoint here
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在 32 位中,内存布局(调试/Windows/内存)为:

64位内存布局

64 位内存布局:

64位内存布局

  • 深蓝色:对象头,包含0x00000001,因为对象被锁定
  • 紫色:方法表,定义类型 ( Player)
  • 绿色:对象的数据,hp此处
  • 浅蓝色:对象的数据,maxHp此处

复制过程

如果您现在继续

var ourAllocatedMemoryPTR = (byte*) Marshal.AllocHGlobal(1024);
var objHandle = GCHandle.Alloc(boxedPlayer, GCHandleType.Pinned);
var adress = objHandle.AddrOfPinnedObject();
var ptr = adress.ToPointer();
Buffer.MemoryCopy(ptr, ourAllocatedMemoryPTR, sizeof(Player), sizeof(Player));
Run Code Online (Sandbox Code Playgroud)

你会看到它ptr指向 0x04a2a770,即 0x04ECA76C + 4(就在数据开始的地方)。sizeof(Player)是8,是两个4字节的int

ourAllocatedMemoryPTR 之前和之后的记忆Buffer.MemoryCopy()

之前和之后的本机内存

奇怪的调试器

在像 WinDbg 这样的调试器中,您会得到以下 32 位结果:

0:009> dd 0486a76c-4 L4
0486a768  00000001 06d68ff8 0000aabb 0000ccdd
^Address  ^Header  ^MT      ^hp      ^maxHP

0:009> ? aabb
Evaluate expression: 43707 = 0000aabb
0:009> ? ccdd
Evaluate expression: 52445 = 0000ccdd

0:009> !do 0486a76c
Name:        BoxedStructInMemory.Player
MethodTable: 06d68ff8
EEClass:     06d58ee4
Size:        16(0x10) bytes
File:        C:\Users\...\BoxedStructInMemory.dll
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
0484697c  4000001        4         System.Int32  1 instance    43707 hp
0484697c  4000002        8         System.Int32  1 instance    52445 maxHP
ThinLock owner 1 (02810B18), Recursive 0
Run Code Online (Sandbox Code Playgroud)

对于 64 位:

0:009> dq 000001a3c93ead50-8 L3
000001a3`c93ead48  00000001`00000000 00007ff8`a2f22180
000001a3`c93ead58  0000ccdd`0000aabb

0:009> !do 000001a3c93ead50
Name:        BoxedStructInMemory.Player
MethodTable: 00007ff8a2f22180
EEClass:     00007ff8a2f1c5e8
Size:        24(0x18) bytes
File:        C:\Users\...\BoxedStructInMemory.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ff8a2e2b1f0  4000001        8         System.Int32  1 instance            43707 hp
00007ff8a2e2b1f0  4000002        c         System.Int32  1 instance            52445 maxHP
ThinLock owner 1 (000001A3C7899930), Recursive 0
Run Code Online (Sandbox Code Playgroud)

  • 绝对值得指出的是,托管对象如何在内存中布局的细节是运行时的专有权限,并且可能会发生变化,而托管代码不会抱怨。如此处所描述的对布局的依赖是不明智的;像“Marshal”这样的类和各种属性的存在允许从托管内存复制到非托管内存而无需知道,而“ref”类型和“Unsafe”之类的东西被添加到最新版本的 C#/.NET 中,以进行低级操作,而无需知道。必须复制。 (2认同)