.net 在C#中分配非托管内存中的对象

pkwftd7m  于 2023-02-01  发布在  .NET
关注(0)|答案(1)|浏览(237)

首先,我知道我甚至不应该尝试这样做,但我喜欢尝试没有意义的事情,我对this post很感兴趣,并根据我的需要进行了修改,我想通过在非托管内存中实现数组分配来改进它。

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

对于值类型(short、int、long等),它可以工作(我还没有尝试过struct),但是它在为对象分配数组后崩溃,如下所示...

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

不知道为什么它崩溃了AccessViolationException(你不能用正常的方法捕捉),最好的部分是它不总是发生。调试时检查堆栈,数组实际上保存了对非托管内存中对象的引用(即使检查地址,它们匹配),但后来它崩溃了(非常频繁)...
有什么建议吗?

    • 更新**

原来在为reference Type分配非托管内存时,你不能赋值或读取嵌套的reference *。可能这与GC管理引用的方式有关(不确定,只是猜测)。这就是为什么在上面的链接中作者将reference封装在struct内部。这样你就在使用值类型,如果你改变数组来存储那些struct(包含你的非托管引用),它就会工作。

  • 嵌套引用指的是类中的字段,您试图在非托管内存和数组元素中分配空间(对象数组本身是堆栈上对堆上实际数组的引用-如果它不是类中的字段-包含"嵌套"引用作为对象元素-也在堆上)
w6mmgewl

w6mmgewl1#

GC将无法更新指向非托管内存中其他对象的指针,并且不将它们视为活引用,因此指向对象中托管内存的任何指针要么无效,要么死亡(两次无效)。正如Marc所说,您唯一可以放置在未Map内存中的是struct s,它遵循unmanaged约束。

相关问题