1

First of all I know I shouldn't even try to do it, but i like to try things that don't make sense. I was intrigued by this post and modified to my needs and I wanted to improve it by implementing allocation of arrays inside unmanaged memory.

public static class Unmanaged<T> where T : class
    {
        private delegate T CreateHandler(IntPtr ptr);
        private delegate IntPtr FindHandler(T obj);

        private static readonly CreateHandler Create;
        private static readonly FindHandler Find;

        private static readonly IntPtr _typePointer;
        private static readonly int _typeSize;

        static Unmanaged()
        {
            Type type = typeof(T);
            _typePointer = type.TypeHandle.Value;
            _typeSize = Marshal.ReadInt32(_typePointer, sizeof(int));

            DynamicMethod method = new DynamicMethod(nameof(Create), typeof(T), new[] { typeof(IntPtr) }, typeof(Unmanaged<T>), true);
            var generator = method.GetILGenerator();
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ret);

            Create = (CreateHandler)method.CreateDelegate(typeof(CreateHandler));

            method = new DynamicMethod(nameof(Find), typeof(IntPtr), new[] { typeof(T) }, typeof(Unmanaged<T>), true);
            generator = method.GetILGenerator();
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ret);

            Find = (FindHandler)method.CreateDelegate(typeof(FindHandler));
        }

        public static T New()
        {
            IntPtr handle = Marshal.AllocHGlobal(_typeSize);
            IntPtr pointer = handle + IntPtr.Size;

            Marshal.WriteIntPtr(pointer, _typePointer);

            return Create(pointer);
        }

        public static void Destroy(T obj)
        {
            IntPtr pointer = Find(obj);
            IntPtr handle = pointer - IntPtr.Size;

            Marshal.FreeHGlobal(handle);
        }
    }
public static class UnmanagedArray<T>
    {
        private static readonly Delegate Create;
        private static readonly Delegate Find;

        private static readonly IntPtr _typePointer;
        private static readonly int _typeSize;

        static UnmanagedArray()
        {
            Type type = typeof(T[]);
            Type createType = Expression.GetFuncType(typeof(IntPtr), typeof(T[]));
            Type findType = Expression.GetFuncType(typeof(T[]), typeof(IntPtr));

            _typePointer = type.TypeHandle.Value;
            _typeSize = Marshal.ReadInt32(_typePointer, sizeof(int));

            DynamicMethod method = new DynamicMethod(nameof(Create), typeof(T[]), new[] { typeof(IntPtr) }, typeof(UnmanagedArray<T>), true);
            ILGenerator generator = method.GetILGenerator();
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ret);
           
            Create = method.CreateDelegate(createType);

            method = new DynamicMethod(nameof(Find), typeof(IntPtr), new[] { typeof(T[]) }, typeof(UnmanagedArray<T>), true);
            generator = method.GetILGenerator();
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ret);

            Find = method.CreateDelegate(findType);
        }

        public static T[] New(int count)
        {
            Type elementType = typeof(T);
            int elementSize = GetElementSize(elementType);
            int size = _typeSize + elementSize * count;
            IntPtr handle = Marshal.AllocHGlobal(size);
            IntPtr pointer = handle + IntPtr.Size;

            Marshal.WriteIntPtr(pointer, _typePointer);

            InitializeArray(handle, size, count);

            return (T[])Create.DynamicInvoke(pointer);
        }

        public static void Destroy(T[] obj)
        {
            IntPtr pointer = (IntPtr)Find.DynamicInvoke(obj);
            IntPtr handle = pointer - IntPtr.Size;

            Marshal.FreeHGlobal(handle);
        }

        private static int GetElementSize(Type type)
        {
            if (type.IsValueType) return Marshal.SizeOf(type);
            else return IntPtr.Size;
        }

        private static unsafe void InitializeArray(IntPtr handle, int size, int count)
        {
            int startPosition = IntPtr.Size * 2;
            int endPosition = IntPtr.Size * 2 + sizeof(int);
            byte byteSize = 8;
            byte* bytes = (byte*)handle;

            for (int index = startPosition, positionIndex = 0; index < endPosition; index++, positionIndex++)
            {
                bytes[index] = (byte)(count >> positionIndex * byteSize);
            }

            for (int index = _typeSize; index < size; index++)
            {
                bytes[index] = 0;
            }
        }
    }

For value types (short, int, long etc.) it works (I haven't tried struct's yet), but it crashes after allocating array for objects as below...

static void Main(string[] args)
        {
            Test test = Unmanaged<Test>.New();
            Test[] array = UnmanagedArray<Test>.New(2);

            test.Value = 5;

            array[0] = test;

            Console.WriteLine(array[0].Value); //AccessViolationException

            Unmanaged<Test>.Destroy(test);
            UnmanagedArray<Test>.Destroy(array);
        }

Don't know why it crashes with AccessViolationException (which You can't catch by normal ways) and the best part is that it's something that not always occurs. Checked stack when debugging and the array actually saves reference to object in unmanaged memory (even checked addresses and they match), but later on it crashes (very often)...

Any suggestions?

Update

Turns out You can't assign or read nested reference* when allocating unmanaged memory for reference Type. Probably it's related to how GC manages references (don't know for sure, it's just a guess). That's why in the link above the author encapsulated reference inside struct. That way You're working with value type and if You change array to store those structs (containing Your unmanaged references) it will work.

*By nested references I mean fields in class which You try to allocate space in unmanaged memory and array elements (array of objects itself is a reference on stack to actual array on heap - if it's not a field in a class - which contains "nested" references as elements to objects - also on heap)

Feralnex
  • 61
  • 5
  • 1
    That question is years old. I haven't kept track of object layout in .NET versions, but it's quite possible the way this purports to construct managed arrays simply doesn't correspond to what the runtime is currently doing. For starters, things between .NET Framework (4.x and earlier) and .NET Core (5 and beyond) are quite different indeed, as are differences between 32-bit and 64-bit. Modern .NET code has better ways to achieve stuff like this -- `Span`, `stackalloc`, `Memory` and things like `Unsafe.As`. I'd abandon the approach of trying to stuff *managed* things in the unmanaged world. – Jeroen Mostert Jan 17 '23 at 21:10
  • 1
    If you can restrict yourself to `` where `T : unmanaged` (which includes `struct` that don't include any references), then this is very achievable - but instead of using arrays, use `Span` and `Memory` (spans are sort of like arrays, but can refer to any contiguous memory range; spans have some limitations in terms of where you can store them so memory is basically an "I can give you a span on demand" accessor. Making a memory from a raw pointer is trivial, which means you can have a memory/span to unmanaged memory, yet never need to use `unsafe` except in the initial alloc – Marc Gravell Jan 17 '23 at 22:55
  • 1
    However, if you specifically want arrays, and/or specifically want classes: then I strongly advise turning around and not doing that – Marc Gravell Jan 17 '23 at 22:56
  • @JeroenMostert thanks for the comment and I know about the stuff You mentioned. I'm just wondering if there's a way to make this possible, if anybody have an idea of how it could work. Maybe I don't know something about missing parts in memory? Or maybe i need to modify some parts of the memory (allocated for the array of objects)? I'm interested in those kind of things. If it won't work no matter what i'll do, then I would like to know why "in depth". – Feralnex Jan 17 '23 at 23:00
  • @MarcGravell why should I turn around? Can You elaborate? – Feralnex Jan 17 '23 at 23:02
  • 1
    @Feralnex because I don't see it ending well, and I see you spending a lot of time trying in frustration - but: your time, your choice, I suppose – Marc Gravell Jan 17 '23 at 23:07
  • @MarcGravell I thought You know something that I should know... But yeah, I'm curious if this approach will work. – Feralnex Jan 17 '23 at 23:10
  • 1
    Again: for `struct` values with no references: this can be a hugely powerful trick for reducing GC overhead - I've used it many times with great success. But mixing it with classes ... just doesn't go well, from everything I've seen – Marc Gravell Jan 17 '23 at 23:14

1 Answers1

1

GC won't be able to update pointers to other objects inside your unmanaged memory and doesn't count them as live references, so any pointer to managed memory inside your object will be either invalid or dead (twice invalid). As Marc said, the only thing you can place in unmaged memory is structs adhering to unmanaged constraint.

OwnageIsMagic
  • 1,949
  • 1
  • 16
  • 31