Copy struct from its boxing into allocated memory?

Viewed 178

Background

Lets say i have a struct. Lets also pretend that we dont know its type. Thats why we have it boxed and we cant unbox it.


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

As far as i know a boxed struct... is not the same size as the struct itself... because it gets boxed and managed. So there some bytes at the head or the tail to define it as an object, a boxed struct. So the example above copies the whole boxed struct into the memory. As far as i know. Which is not what we want.

The question

Is it possible to copy just the struct inside the box into the allocated memory ? Our new allocated memory should just store the struct, not a boxed one. I thought it probably possible by cutting the struct out of the its box ? Cutting of the part or the head/tail that defines it as a box during copy ?

Is this possible ? How exactly does a struct gets boxed ? How many bytes are added before and after it ? How does it look like in memory ?

Glad for any help ! Thanks :)

1 Answers

Do not use internals

The exact internal layout is subject to change. Methods like GCHandle.AddrOfPinnedObject() are designed to give you a pointer to the object's data, not to some internal stuff like headers, method table or padding bytes. So, just use those methods, do not do the math yourself.

Retrieves the address of object data in a Pinned handle.

emphasis mine

But it's interesting anyway

At the time of writing (and several years before), a .NET object in memory has the following layout:

  • -pointersize: header
  • 0: method table (type of the object)
  • +pointersize: object data

You can see that with the following code. I have simplified the fields a bit, so that we can see patterns easier than with a 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
        }
    }
}

In 32 Bit, the memory layout (Debug/Windows/Memory) is:

64 bit memory layout

64 Bit memory layout:

64 bit memory layout

  • dark blue: object header, containing 0x00000001 because the object is locked
  • violet: method table, defining the type (Player)
  • green: data of the object, hp here
  • light blue: data of the object, maxHp here

The copying process

If you now continue with

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

you'll see that ptr points to 0x04a2a770, which is 0x04ECA76C + 4 (just where the data begins). sizeof(Player) is 8, which is for the two 4-byte ints.

Memory of ourAllocatedMemoryPTR before and after Buffer.MemoryCopy() :

Native memory before and after

Strange debuggers

In a debugger like WinDbg, you get the following results for 32 bit:

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

And for 64 bit:

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
Related