Reputation: 61
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)
Upvotes: 1
Views: 530
Reputation: 2299
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 struct
s adhering to unmanaged
constraint.
Upvotes: 3