This is a pretty basic class to allocate objects from the native heap. The whole process goes as follows: allocate bytes from NativeMemory.Alloc, set the method table pointer from an existing object, and call the constructor through reflection. Currently, it only works for objects with a parameterless constructor.
The reason for this class is to bring manual memory management to C#. Currently, the only way to allocate objects is from the GC heap which incurs garbage collection penalties. While it does lend a hand to preventing memory leaks, there is a cost to leaving data in memory after it is no longer needed (and why .NET provides utilities like NativeMemory to allocate on the native heap). (SecureString is one example of a class that can't leave it's contents in memory) Of course, this class does not provide any memory safety in the form of leak prevention and requires manual deallocation before all references are deleted. It could of course be used like in C++ with allocations in the constructors and deallocations in destructors (or disposers for C#). You could even create classes for automatic memory management like std::unique_ptr and std::shared_ptr and benefit from semi-managed memory that does not incur GC penalties.
I know that calling the constructor through reflection is an expensive operation, could I get the raw function pointer and call that directly? Also, I suppose I could add parameterized constructors through a similar method, but I would ideally want to reduce the number of gc allocations. This seems to be problematic as the reflective constructor takes in an object[] for the parameters. I have experimented with using a static field for this purpose to keep an allocated object[], but without the thread_local storage class, this seems to be a problem.
I have heard information mentioning an object header at a negative index (for gc). So far, I have not had issues with gc, but I have only done small tests. If a gc was to occur, would it need to access the information or does it just ignore it entirely. Also, would a gc try to deallocate it if it was out of scope (from what I'm reading about mark-sweep algorithms, this doesn't seem likely, but it is Microsoft)?
Enough waiting, here's the code:
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace InfinityOfficialNetwork.Shared.Memory
{
public sealed class Allocator : IAllocator //the IAllocator is just an interface with the methods listed, it can be removed
{
//static methods for generic methods
private class AllocatorHelper<TAlloc>
{
//size of type
public static readonly unsafe int typeSize = Unsafe.SizeOf<TAlloc>();
//type fo object
public static readonly Type objectType;
//constructor
public static readonly ConstructorInfo objectConstructor;
//destructor
public static readonly MethodInfo objectDestructor;
//method table pointer, this is needed to call methods from reflection (and anything else listed in the method table)
public static nint methodTablePointer;
static unsafe AllocatorHelper()
{
//get constructor / destructor
objectType = typeof(TAlloc);
objectConstructor = objectType.GetConstructor(Type.EmptyTypes);
objectDestructor = objectType.GetMethod("Finalize", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new ArgumentNullException();
//get an uninitialized instance for the method table pointer
object obj = RuntimeHelpers.GetUninitializedObject(typeof(TAlloc));
nint ptr = ((nint*)(&obj))[0];//get the pointer
methodTablePointer = ptr;
}
}
public unsafe TAlloc* AllocatePointer<TAlloc>() where TAlloc : new()
{
//allocate memory
nint o = Marshal.AllocHGlobal(AllocatorHelper<TAlloc>.typeSize);
if (o == 0)
throw new OutOfMemoryException();//should I throw something else?
//set method table pointer
((nint*)o)[0] = AllocatorHelper<TAlloc>.methodTablePointer;
//call parameterless constructor
AllocatorHelper<TAlloc>.objectConstructor.Invoke(*(TAlloc*)o, null);
return (TAlloc*)(o);
}
public unsafe TAlloc* AllocateArray<TAlloc>(int Count) where TAlloc : new()
{
//does the same thing but for an array of objects
nint o = Marshal.AllocHGlobal(AllocatorHelper<TAlloc>.typeSize * Count);
if (o == 0)
throw new OutOfMemoryException();
for (int i = 0; i < Count; i++)
{
((nint*)(&(((byte*)o)[AllocatorHelper<TAlloc>.typeSize * i])))[0] = AllocatorHelper<TAlloc>.methodTablePointer;
AllocatorHelper<TAlloc>.objectConstructor.Invoke(((TAlloc*)o)[i], null);
}
return (TAlloc*)(o);
}
//returns by reference instead of by ptr
public unsafe ref TAlloc Allocate<TAlloc>() where TAlloc : new() => ref *AllocatePointer<TAlloc>();
public unsafe void DeAllocatePointer<TAlloc>(TAlloc* o)
{
//call destructor and free
AllocatorHelper<TAlloc>.objectDestructor.Invoke(*o, null);
Marshal.FreeHGlobal((nint)o);
}
public unsafe void DeAllocateArray<TAlloc>(TAlloc* o, int Count)
{//same thing but for the entire array
for (int i = 0; i < Count; i++)
{
AllocatorHelper<TAlloc>.objectDestructor.Invoke((o)[i], null);
}
Marshal.FreeHGlobal((nint)o);
}
//same thing for by-ref objs
public unsafe void DeAllocate<TAlloc>(ref TAlloc o) => DeAllocatePointer<TAlloc>((TAlloc*)Unsafe.AsPointer(ref o));
}
}
stackalloc
? \$\endgroup\$