【问题标题】:Copy struct from its boxing into allocated memory?将结构从其装箱复制到分配的内存中?
【发布时间】:2021-06-28 18:51:35
【问题描述】:

背景

假设我有一个结构。让我们也假装我们不知道它的类型。这就是为什么我们将它装箱而无法拆箱的原因。


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();

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

问题

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

这可能吗?结构究竟是如何装箱的?在它之前和之后添加了多少字节?它在记忆中的样子如何?

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

【问题讨论】:

  • 看来你已经有相当多的代码了。为什么不运行代码并查看调试器中的内存?
  • @ThomasWeller Havent 找到了一种统一的方法:/ 但我认为它只复制盒装结构。这是有道理的。或者你怎么看?
  • Marshal.StructureToPtr 只复制未装箱的部分呢?你到底想做什么,为什么需要非托管内存中的结构?
  • @Charlieface 如果我能解释一切……我可以交出一整本书。 Marshal.StructureToPtr 是否能够复制盒装结构?这就是重点......从它的装箱中删除结构并只复制结构......在编译时不知道它的类型^^
  • 您无需解释所有内容,只需解释您在此处尝试执行的操作即可。你有一个预先存在的缓冲区要复制到(似乎是这样),你如何分配足够的空间(你当前的代码显然不起作用,我建议你使用Marshal.SizeOf,它与sizeof不同),你在打电话给DllImport(然后就使用标准编组器)

标签: c# memory struct


【解决方案1】:

不要使用内部构件

确切的内部布局可能会发生变化。 GCHandle.AddrOfPinnedObject() 之类的方法旨在为您提供指向对象数据的指针,而不是指向某些内部内容,例如标头、方法表或填充字节。所以,只要使用这些方法,不要自己做数学题。

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

强调我的

不过还是挺有意思的

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

  • -指针大小:标题
  • 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 位内存布局:

  • 深蓝色:对象头,包含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));

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

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

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

【讨论】:

  • 非常感谢! :) 您能否添加一个描述/示例,说明我们如何将结构从装箱结构复制到分配的内存中?没有那些描述对象的字节?
  • @genaray:我想说你的其余代码对于 C++ CLI 来说都很好。对于 P/Invoke,您应该使用 Marshal.SizeOf。我不是这方面的专家。
  • 谢谢!那么这是我们将结构复制到分配的内存中的正确方法吗?这不会从盒子中复制字节吗?只是原始结构?不是一个盒装的进入记忆?
  • 绝对值得指出的是,托管对象在内存中的布局细节是运行时的专有权限,并且可能会发生变化,而托管代码不会抱怨。如此处描述的依赖布局是不明智的; Marshal 之类的类和各种属性的存在是为了允许在无需知道的情况下从托管内存复制到非托管内存,而诸如 ref 类型和 Unsafe 之类的东西已添加到最新版本的 C#/.NET 中以进行低级操作,而无需必须复制。
  • 我同意@JeroenMostert 这是不可靠的。如果你想将整个结构复制到非托管内存,你应该使用Marshal
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-21
  • 2013-06-25
  • 1970-01-01
相关资源
最近更新 更多