How to get a pointer to managed object in C#

Sometimes when optimizing stuff we can stuck on the border beating concrete wall of language limitations.

C# doesn’t allow to take pointers of managed objects, like C++ do. Of course C++ objects are unmanaged/native ones.

When object is created somewhere, you can take a pointer, pass it somewhere, store into arrays, etc.

But also, you can access fields of the object via this pointer, even if you don’t know what’s the type.

Go straight to the solution

Introduction for pointers in C++/C# (structs)
class MyStuff{
public:
  int field;
};

MyStuff obj;
obj.field = 42;
MyStuff* objPtr = &obj;
uint8_t *ptr = (uint8_t*)objPtr;

auto offsetOfField = offsetof(MyStuff, field);
auto fieldPtr = (int*)(ptr + offsetOfField); 
// *fieldPtr == 42;
*fieldPtr = 8192;
//obj.field == 8192;

offsetof makes it possible to take field offsets of known type, that’s quite known fact, I bet you know it before.

Everything is just a bunch of bytes.

Ok, but you can do just the same with reflection in C#

You might say

Yeah, totally possible:

public class MyClass{
  public int field = 42;
}

var obj = new MyClass();
var fieldInfo = obj.GetType().GetField("field");
var value = fieldInfo.GetValue(obj); //42
fieldInfo.SetValue(obj, 8192); //obj.field is now 8192

Seems quite straightforward, and even error-prone

But.

This kind of access require to:

  1. Have RTTI info about the type
  2. Obtain field info though reflection -> generate garbage
  3. Box the value -> generate garbage
    • if using structs as object type, boxing is also possible

As we can see, there’s some underground stuff is going on. Can we avoid at least some GC with TypedReference and Get/SetValueDirect? Yeah, we can! But not in Unity’s IL2CPP

Ok, but, can we avoid GC at all?

Yes! C# does it greatly with structs:

[StructLayout(LayoutKind.Sequential)]
public struct MyStruct{
  private int field;
  public MyStruct(int initialValue){
    field = initialValue;
  }

  public int GetValue() => field;
}

var offset = Marshal.OffsetOf<MyStruct>("field").ToInt32();
var obj = new MyStruct(42);
MyStruct *structPtr = &obj;
var fieldPtr = (int*)((byte*)structPtr + offset); //*fieldPtr == 42
*fieldPtr = 8192;
obj.GetValue(); // 8192

Great, no GC (well, beside of strings and allocating new struct, which could be placed on heap in some cases)

If we have RTTI it’s quite easy to get offsets, using similar to C’s offsetof construction from Marshal.

What if we don’t have RTTI? Well, then we just calculate offset manually, not a great deal, but this could be platform-dependent. There’s tons of info and catch-ups of fields alignment in C/C#, fortunately C# follows the same rules as C structs.

C# is GREATLY designed language to maintain interoperability with C/C++. And this is biggest advantage of this language in my opinion.

Ok, C# allows pointers over structs, but can we get a pointer to CLR class object?

Yes, definitely.

Disclaimer: Always pin CLR objects unless you are sure that this won’t blow up. It’s all about GC. Simply speaking .NET GC is compacting/moving one, so it can move your objects in memory (rendering your pointers invalid) as far as it want to. Unless you pin them, or the object/array is >85KB (implementation defined), those are never moved and stored in special large heap.

Unity’s GC (and one of the Mono’s ones) is non-moving. Better for us, we can be sure that our object/array won’t be moved. Unless you build for WinRT or where real .NET is used instead of Mono.

So, what’s our first attempt?

public class MyClass{
  public int field;
}

var objPtr = &obj;

Kinda naive, that won’t even compile!

What’s next?

public struct WrappingStruct{
  public object obj;
}

var ws = new WrappingStruct();
ws.obj = obj;
var wsPtr = &ws;
IntPtr objPtr = *(IntPtr*)wsPtr;

Oh! That would work. But it feels awkward, doesn’t it?

Here’s another one:

object[] objects = new object[]{obj};
IntPtr *arrayPtr = (IntPtr*)Marshal.UnsafeAddrOfPinnedArrayElement(objects, 0);
var objPtr = arrayPtr[0];
// Yay! We have the object!

Ok, but how can I get pointer to the field?

//Easy!
var offset = sizeof(IntPtr) * 2 + 0;
var fieldPtr = (int*)((byte*)objPtr + offset); //*fieldPtr = 42;
*fieldPtr = 8192;
//obj.field is now 8192 

Perfect, can we make it better?

Sure!

/*
 Also works for real .NET, but it's implementation defined.
 This structure contains a header for CLR class objects, 
 It stores some simple metadata, vtable for virtual functions etc,
 It's greatly observed in books about CLR
*/
[StructLayout(LayoutKind.Sequential)]
public struct MonoHeader{
  private IntPtr __mono1;
  private IntPtr __mono2;
}

[StructLayout(LayoutKind.Sequential)]
public struct MyClassAccessor{
  // offset: 0, size: 16 (on x64), 8 (on x86)
  private MonoHeader _header;
  public int field;
}

var objAccessor = (MyClassAccessor*)objPtr;
objAccessor->field = 999999;
//obj.field is 999999 now.

Isn’t that nice? Good old memory access is back.

This technique could be used in numerous amount ways.

Ok, but it’s still awkward to create arrays and wrapper structs to obtain a pointer, isn’t it?

Right.

Here’s the trick, you can’t just use something like objPtr = &obj in C#. But you can in IL! And I’ll show you how.

  1. Create dummy project with two function members. Or just one, if you’ll only use it in plain C#, without Unity
  2. Compile assembly
  3. Open assembly in dnSpy
  4. Select the class in its namespace
using System;
using System.Runtime.CompilerServices;

// Token: 0x02000002 RID: 2
public static class PointerLibExtension
{
	// Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
	[MethodImpl(MethodImplOptions.AggressiveInlining)]
	public unsafe static void* GetPointer(this object obj)
	{
		return null;
	}

	// Token: 0x06000002 RID: 2 RVA: 0x00002054 File Offset: 0x00000254
	[MethodImpl(MethodImplOptions.AggressiveInlining)]
	public unsafe static void* GetInternalPointer(this object obj)
	{
		return null;
	}
}

Select first method and right click -> Edit Method Body. This will brought you to the IL editor.

Then recreate/paste this instructions:

ldarga.s obj (0)
conv.u
ldind.i
ret

After clicking “Ok” your method will become an illegal (but perfectly working) C# construction!

public unsafe static void* GetPointer(this object obj)
{
	return *(IntPtr*)(&obj);
}

(note that it first take a pointer to a pointer, object is basically a pointer, and then dereference it, this is done for IL2CPP compatability, otherwise you’ll get compile errors). Any self-respecting compiler would optimize it anyway.

Why is it better not to use generic methods for Unity?

The thing is that IL2CPP will generate function meta-data initialization for every generic method, even if no metadata is needed, and this hurts performance badly, when executing code in tight-loops or performance critical sections, also it’s worse for inlining

Now to the internal pointer. What’s internal pointer? In Unity it’s pointer to the native object, since CLR objects are just wrappers/handles for native ones. It’s located right after the header, you can get it by name “UnityEngine.Object.m_cachedPtr”, unfortunatelly it’s hidden under private access modifier.

UnityEngine.Object class memory layout
class UnityObject{
  private IntPtr pointerToNativeEngineObject;
}

Select “GetInternalPointer” method, and do the same thing as with previous method, but with different IL code:

ldarga.s obj(0)
conv.u
ldind.i
sizeof [mscorlib]System.IntPtr
ldc.i4.2
mul
add
ldind.i
ret
Output
public unsafe static void* GetInternalPointer(this object obj)
{
	return *(*(IntPtr*)(&obj) + (IntPtr)(sizeof(IntPtr) * 2));
}

It’s a little bit more complicated, basically we take first pointer-sized values with offset of sizeof(IntPtr) * 2 (which is the size of Mono/.NET header).

Now, save the assembly somewhere and reference it within your project, Unity or plain C# project, whatever.

Here’s simple usage:

var obj = new MyClass();
var ptr = obj.GetPointer();

The result would be the same as for our previous detours, but much more clean and optimized. You won’t pay anything for taking a pointer.

Let’s make it a little bit more clean and visual:

class MyClass{
  private int field = 42;
}

[StructLayout(LayoutKind.Sequential)]
struct MyClassAccess{
  private MonoHeader __header; //basically two IntPtrs
  public int field;
}

var obj = new MyClass();
var access = (MyClassAccess*)obj.GetPointer();
access->field = 2023; //obj.field is now 2023

Now we can do whatever we like with the class objects, even if we don’t have access to them, and can’t modify assembly. But more important, we can pass those pointers to native code or Burst (Unity)!

More on use cases of that in Unity would be observed in next posts.

Compiled library could be found on GitHub

Cheers!

2 thoughts to “How to get a pointer to managed object in C#”

  1. Here’s an example of how you can access the Gradient
    class data in Unity.

    namespace LSS2D
    {
        [StructLayout(LayoutKind.Sequential)]
        public unsafe struct GradientStruct
        {
            private fixed byte colors[sizeof(float) * 4 * 8];
            private fixed byte colorTimes[sizeof(ushort) * 8];
            private fixed byte alphaTimes[sizeof(ushort) * 8];
            private byte colorCount;
            private byte alphaCount;
            private GradientMode mode;
    
            public Color* Colors
            {
                get
                {
                    fixed (byte* colorsPtr = colors)
                        return ((Color*)colorsPtr);
                }
            }
    
            public ushort* ColorTimes
            {
                get
                {
                    fixed (byte* colorTimesPtr = colorTimes)
                        return (ushort*)colorTimesPtr;
                }
            }
    
            public ushort* Alphas
            {
                get
                {
                    fixed (byte* alphaTimesPtr = alphaTimes)
                        return (ushort*)alphaTimesPtr;
                }
            }
    
            public ushort* AlphaTimes
            {
                get
                {
                    fixed (byte* alphaTimesPtr = alphaTimes)
                        return (ushort*)alphaTimesPtr;
                }
            }
    
            public int ColorCount
            {
                get => colorCount;
                set => colorCount = (byte)value;
            }
    
            public int AlphaCount
            {
                get => alphaCount;
                set => alphaCount = (byte)value;
            }
    
            public GradientMode Mode
            {
                get => mode;
                set => mode = value;
            }
    
            public void SetColorKey(int index, GradientColorKey value)
            {
                ThrowOnIncorrectIndex(index);
                
                var clr = value.color;
                clr.a = Colors[index].a;
    
                Colors[index] = clr;
                ColorTimes[index] = (ushort)(65535 * value.time);
            }
    
            public GradientColorKey GetColorKey(int index)
            {
                ThrowOnIncorrectIndex(index);
                return new GradientColorKey(Colors[index], ColorTimes[index] / 65535f);
            }
    
            public void SetAlphaKey(int index, GradientAlphaKey value)
            {
                ThrowOnIncorrectIndex(index);
                Colors[index].a = value.alpha;
                AlphaTimes[index] = (ushort)(65535 * value.time);
            }
    
            public GradientAlphaKey GetAlphaKey(int index)
            {
                ThrowOnIncorrectIndex(index);
                return new GradientAlphaKey(Colors[index].a, AlphaTimes[index] / 65535f);
            }
    
            private static void ThrowOnIncorrectIndex(int idx)
            {
                #if ENABLE_UNITY_COLLECTIONS_CHECKS
                if(idx > 7)
                    throw new Exception("The index should be between 0 and 7.");
                #endif
            }
        }
    
        public static class GradientExt
        {
            public static unsafe GradientStruct* GetAccess(this Gradient gradient)
            {
                return (GradientStruct*)gradient.GetUnityNativePtr();
            }
        }
    
        public static class ColorExt
        {
            public static float4 ToFloat4(this Color color)
            {
                return new float4(color.r, color.g, color.b, color.a);
            }
        }
    }
    

Leave a Reply

Your email address will not be published. Required fields are marked *