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

时间:2021-04-01 19:02:28

标签: 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();

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

问题

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

这可能吗?结构体究竟是如何装箱的?在它之前和之后添加了多少字节?它在内存中看起来如何?

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

1 个答案:

答案 0 :(得分:3)

不要使用内部结构

确切的内部布局可能会发生变化。 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
        }
    }
}

在 32 位中,内存布局(Debug/Windows/Memory)是:

64 bit memory layout

64 位内存布局:

64 bit memory layout

  • 深蓝色:对象头,包含 0x00000001,因为对象被锁定
  • 紫:方法表,定义类型(Player
  • green:对象的数据,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));

您会看到 ptr 指向 0x04a2a770,即 0x04ECA76C + 4(数据开始的地方)。 sizeof(Player) 为 8,表示两个 4 字节的 int

ourAllocatedMemoryPTR 前后 Buffer.MemoryCopy() 的记忆:

Native memory before and after

奇怪的调试器

在像 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

对于 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