Fix native to managed marshalling of System.Array

This commit is contained in:
2023-02-19 20:21:54 +02:00
parent 1c74e89f0d
commit 87e9046e41
3 changed files with 109 additions and 43 deletions

View File

@@ -368,7 +368,7 @@ namespace FlaxEditor.Content.Import
MaxSize = managed.MaxSize,
TextureGroup = managed.TextureGroup,
Size = managed.Size,
SpriteAreas = managed.SpriteAreas != IntPtr.Zero ? ((ManagedArray)ManagedHandle.FromIntPtr(managed.SpriteAreas).Target).GetArray<Rectangle>() : null,
SpriteAreas = managed.SpriteAreas != IntPtr.Zero ? ((ManagedArray)ManagedHandle.FromIntPtr(managed.SpriteAreas).Target).ToArray<Rectangle>() : null,
SpriteNames = managed.SpriteNames != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<string>((ManagedArray)ManagedHandle.FromIntPtr(managed.SpriteNames).Target) : null,
};
}

View File

@@ -167,6 +167,7 @@ namespace FlaxEngine
{
private ManagedHandle pinnedArrayHandle;
private IntPtr unmanagedData;
private Type elementType;
private int elementSize;
private int length;
@@ -185,11 +186,11 @@ namespace FlaxEngine
return managedArray;
}
internal static ManagedArray AllocateNewArray(int length, int elementSize)
=> new ManagedArray((IntPtr)NativeInterop.NativeAlloc(length, elementSize), length, elementSize);
internal static ManagedArray AllocateNewArray(int length, Type elementType)
=> new ManagedArray((IntPtr)NativeInterop.NativeAlloc(length, Marshal.SizeOf(elementType)), length, elementType);
internal static ManagedArray AllocateNewArray(IntPtr ptr, int length, int elementSize)
=> new ManagedArray(ptr, length, elementSize);
internal static ManagedArray AllocateNewArray(IntPtr ptr, int length, Type elementType)
=> new ManagedArray(ptr, length, elementType);
/// <summary>
/// Returns an instance of ManagedArray from shared pool.
@@ -213,7 +214,7 @@ namespace FlaxEngine
public static ManagedArray AllocatePooledArray<T>(int length) where T : unmanaged
{
ManagedArray managedArray = ManagedArrayPool.Get();
managedArray.Allocate((IntPtr)NativeInterop.NativeAlloc(length, Unsafe.SizeOf<T>()), length, Unsafe.SizeOf<T>());
managedArray.Allocate((IntPtr)NativeInterop.NativeAlloc(length, Unsafe.SizeOf<T>()), length, typeof(T));
return managedArray;
}
@@ -224,26 +225,29 @@ namespace FlaxEngine
pinnedArrayHandle = ManagedHandle.Alloc(arr, GCHandleType.Pinned);
unmanagedData = Marshal.UnsafeAddrOfPinnedArrayElement(arr, 0);
length = arr.Length;
elementSize = Marshal.SizeOf(arr.GetType().GetElementType());
elementType = arr.GetType().GetElementType();
elementSize = Marshal.SizeOf(elementType);
}
internal void Allocate<T>(T* ptr, int length) where T : unmanaged
{
unmanagedData = new IntPtr(ptr);
this.length = length;
elementType = typeof(T);
elementSize = Unsafe.SizeOf<T>();
}
internal void Allocate(IntPtr ptr, int length, int elementSize)
internal void Allocate(IntPtr ptr, int length, Type elementType)
{
unmanagedData = ptr;
this.length = length;
this.elementSize = elementSize;
this.elementType = elementType;
elementSize = Marshal.SizeOf(elementType);
}
private ManagedArray() { }
private ManagedArray(IntPtr ptr, int length, int elementSize) => Allocate(ptr, length, elementSize);
private ManagedArray(IntPtr ptr, int length, Type elementType) => Allocate(ptr, length, elementType);
~ManagedArray()
{
@@ -272,15 +276,46 @@ namespace FlaxEngine
ManagedArrayPool.Put(this);
}
internal IntPtr GetPointer => unmanagedData;
internal IntPtr Pointer => unmanagedData;
internal int Length => length;
internal int ElementSize => elementSize;
public Span<T> GetSpan<T>() where T : struct => new Span<T>(unmanagedData.ToPointer(), length);
public Span<T> ToSpan<T>() where T : struct => new Span<T>(unmanagedData.ToPointer(), length);
public T[] GetArray<T>() where T : struct => new Span<T>(unmanagedData.ToPointer(), length).ToArray();
public T[] ToArray<T>() where T : struct => new Span<T>(unmanagedData.ToPointer(), length).ToArray();
public Array ToArray() => ArrayCast.ToArray(new Span<byte>(unmanagedData.ToPointer(), length * elementSize), elementType);
/// <summary>
/// Creates an Array of the specified type from span of bytes.
/// </summary>
private static class ArrayCast
{
delegate Array GetDelegate(Span<byte> span);
[ThreadStatic] private static Dictionary<Type, GetDelegate> delegates;
internal static Array ToArray(Span<byte> span, Type type)
{
if (delegates == null)
delegates = new();
if (!delegates.TryGetValue(type, out var deleg))
{
deleg = typeof(ArrayCastInternal<>).MakeGenericType(type).GetMethod(nameof(ArrayCastInternal<int>.ToArray), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<GetDelegate>();
delegates.Add(type, deleg);
}
return deleg(span);
}
private static class ArrayCastInternal<T> where T : struct
{
internal static Array ToArray(Span<byte> span)
{
return MemoryMarshal.Cast<byte, T>(span).ToArray();
}
}
}
/// <summary>
/// Provides a pool of pre-allocated ManagedArray that can be re-used.
@@ -688,7 +723,9 @@ namespace FlaxEngine
[CustomMarshaller(typeof(Array), MarshalMode.ManagedToUnmanagedIn, typeof(SystemArrayMarshaller.ManagedToNative))]
[CustomMarshaller(typeof(Array), MarshalMode.UnmanagedToManagedOut, typeof(SystemArrayMarshaller.ManagedToNative))]
public static class SystemArrayMarshaller
[CustomMarshaller(typeof(Array), MarshalMode.ManagedToUnmanagedOut, typeof(SystemArrayMarshaller.NativeToManaged))]
[CustomMarshaller(typeof(Array), MarshalMode.UnmanagedToManagedIn, typeof(SystemArrayMarshaller.NativeToManaged))]
public static unsafe class SystemArrayMarshaller
{
public struct ManagedToNative
{
@@ -711,13 +748,42 @@ namespace FlaxEngine
public void Free()
{
if (managedArray != null)
{
managedArray.FreePooled();
handle.Free();
}
if (managedArray == null)
return;
managedArray.FreePooled();
handle.Free();
}
}
public struct NativeToManaged
{
ManagedHandle handle;
ManagedArray managedArray;
public void FromUnmanaged(IntPtr unmanaged)
{
if (unmanaged == IntPtr.Zero)
return;
handle = ManagedHandle.FromIntPtr(unmanaged);
}
public Array ToManaged()
{
if (!handle.IsAllocated)
return null;
managedArray = Unsafe.As<ManagedArray>(handle.Target);
return managedArray.ToArray();
}
public void Free()
{
if (!handle.IsAllocated)
return;
managedArray.Free();
handle.Free();
}
}
}
[CustomMarshaller(typeof(Dictionary<,>), MarshalMode.ManagedToUnmanagedIn, typeof(DictionaryMarshaller<,>.ManagedToNative))]
@@ -807,7 +873,7 @@ namespace FlaxEngine
if (unmanaged == null)
return ReadOnlySpan<TUnmanagedElement>.Empty;
ManagedArray managedArray = Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(new IntPtr(unmanaged)).Target);
return managedArray.GetSpan<TUnmanagedElement>();
return managedArray.ToSpan<TUnmanagedElement>();
}
public static void Free(TUnmanagedElement* unmanaged)
@@ -824,7 +890,7 @@ namespace FlaxEngine
if (unmanaged == null)
return Span<TUnmanagedElement>.Empty;
ManagedArray managedArray = Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(new IntPtr(unmanaged)).Target);
return managedArray.GetSpan<TUnmanagedElement>();
return managedArray.ToSpan<TUnmanagedElement>();
}
}
@@ -850,7 +916,7 @@ namespace FlaxEngine
if (unmanaged == null)
return Span<TUnmanagedElement>.Empty;
ManagedArray managedArray = Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(new IntPtr(unmanaged)).Target);
return managedArray.GetSpan<TUnmanagedElement>();
return managedArray.ToSpan<TUnmanagedElement>();
}
public static void Free(TUnmanagedElement* unmanaged)
@@ -884,7 +950,7 @@ namespace FlaxEngine
{
if (unmanagedArray == null)
return Span<TUnmanagedElement>.Empty;
return unmanagedArray.GetSpan<TUnmanagedElement>();
return unmanagedArray.ToSpan<TUnmanagedElement>();
}
public TUnmanagedElement* ToUnmanaged() => (TUnmanagedElement*)ManagedHandle.ToIntPtr(handle);
@@ -900,7 +966,7 @@ namespace FlaxEngine
{
if (unmanagedArray == null)
return ReadOnlySpan<TUnmanagedElement>.Empty;
return unmanagedArray.GetSpan<TUnmanagedElement>();
return unmanagedArray.ToSpan<TUnmanagedElement>();
}
public Span<T> GetManagedValuesDestination(int numElements) => managedArray;
@@ -934,7 +1000,7 @@ namespace FlaxEngine
if (unmanaged == null)
return Span<TUnmanagedElement>.Empty;
ManagedArray unmanagedArray = Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(new IntPtr(unmanaged)).Target);
return unmanagedArray.GetSpan<TUnmanagedElement>();
return unmanagedArray.ToSpan<TUnmanagedElement>();
}
public static T[]? AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements) => unmanaged is null ? null : new T[numElements];
@@ -946,7 +1012,7 @@ namespace FlaxEngine
if (unmanaged == null)
return ReadOnlySpan<TUnmanagedElement>.Empty;
ManagedArray array = Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(new IntPtr(unmanaged)).Target);
return array.GetSpan<TUnmanagedElement>();
return array.ToSpan<TUnmanagedElement>();
}
public static void Free(TUnmanagedElement* unmanaged)
@@ -1129,7 +1195,7 @@ namespace FlaxEngine
internal static T[] GCHandleArrayToManagedArray<T>(ManagedArray ptrArray) where T : class
{
Span<IntPtr> span = ptrArray.GetSpan<IntPtr>();
Span<IntPtr> span = ptrArray.ToSpan<IntPtr>();
T[] managedArray = new T[ptrArray.Length];
for (int i = 0; i < managedArray.Length; i++)
managedArray[i] = span[i] != IntPtr.Zero ? (T)ManagedHandle.FromIntPtr(span[i]).Target : default;
@@ -1444,7 +1510,7 @@ namespace FlaxEngine
internal static Array ToManagedArray(ManagedArray nativeArray)
{
T[] arr = new T[nativeArray.Length];
IntPtr nativePtr = nativeArray.GetPointer;
IntPtr nativePtr = nativeArray.Pointer;
for (int i = 0; i < arr.Length; i++)
{
toManagedTypedMarshaller(ref arr[i], nativePtr, false);
@@ -1651,11 +1717,11 @@ namespace FlaxEngine
{
ManagedArray managedArray = Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(nativePtr).Target);
if (ArrayFactory.GetMarshalledType(elementType) == elementType)
managedValue = Unsafe.As<T[]>(managedArray.GetArray<T>());
managedValue = Unsafe.As<T[]>(managedArray.ToArray<T>());
else if (elementType.IsValueType)
managedValue = Unsafe.As<T[]>(MarshalHelper<T>.ToManagedArray(managedArray));
else
managedValue = Unsafe.As<T[]>(MarshalHelper<T>.ToManagedArray(managedArray.GetSpan<IntPtr>()));
managedValue = Unsafe.As<T[]>(MarshalHelper<T>.ToManagedArray(managedArray.ToSpan<IntPtr>()));
}
else
managedValue = null;
@@ -1719,7 +1785,7 @@ namespace FlaxEngine
if (nativePtr != IntPtr.Zero)
{
ManagedArray managedArray = Unsafe.As<ManagedArray>(ManagedHandle.FromIntPtr(nativePtr).Target);
managedValue = Unsafe.As<T[]>(MarshalHelper<T>.ToManagedArray(managedArray.GetSpan<IntPtr>()));
managedValue = Unsafe.As<T[]>(MarshalHelper<T>.ToManagedArray(managedArray.ToSpan<IntPtr>()));
}
else
managedValue = null;
@@ -1758,8 +1824,8 @@ namespace FlaxEngine
else if (elementType.IsValueType)
{
// Convert array of custom structures into internal native layout
managedArray = ManagedArray.AllocateNewArray(arr.Length, Marshal.SizeOf(marshalledType));
IntPtr managedArrayPtr = managedArray.GetPointer;
managedArray = ManagedArray.AllocateNewArray(arr.Length, marshalledType);
IntPtr managedArrayPtr = managedArray.Pointer;
for (int i = 0; i < arr.Length; i++)
{
MarshalToNative(arr.GetValue(i), managedArrayPtr, elementType);
@@ -2214,7 +2280,7 @@ namespace FlaxEngine
Type marshalledType = ArrayFactory.GetMarshalledType(type);
if (marshalledType.IsValueType)
{
ManagedArray managedArray = ManagedArray.AllocateNewArray((int)size, Marshal.SizeOf(marshalledType));
ManagedArray managedArray = ManagedArray.AllocateNewArray((int)size, marshalledType);
return ManagedHandle.Alloc(managedArray/*, GCHandleType.Weak*/);
}
else
@@ -2234,7 +2300,7 @@ namespace FlaxEngine
if (managedArray.Length == 0)
return IntPtr.Zero;
Assert.IsTrue(index >= 0 && index < managedArray.Length);
return IntPtr.Add(managedArray.GetPointer, size * index);
return IntPtr.Add(managedArray.Pointer, size * index);
}
[UnmanagedCallersOnly]
@@ -2454,8 +2520,8 @@ namespace FlaxEngine
internal static void SetArrayValueReference(ManagedHandle arrayHandle, IntPtr elementPtr, IntPtr valueHandle)
{
ManagedArray managedArray = Unsafe.As<ManagedArray>(arrayHandle.Target);
int index = (int)(elementPtr.ToInt64() - managedArray.GetPointer.ToInt64()) / managedArray.ElementSize;
managedArray.GetSpan<IntPtr>()[index] = valueHandle;
int index = (int)(elementPtr.ToInt64() - managedArray.Pointer.ToInt64()) / managedArray.ElementSize;
managedArray.ToSpan<IntPtr>()[index] = valueHandle;
}
[UnmanagedCallersOnly]

View File

@@ -1526,23 +1526,23 @@ namespace Flax.Build.Bindings
// Marshal blittable array elements back to original non-blittable elements
string originalElementTypeMarshaller = originalElementType + "Marshaller";
string internalElementType = $"{originalElementTypeMarshaller}.{originalElementType}Internal";
toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.NativeArrayToManagedArray<{originalElementType}, {internalElementType}>(((ManagedArray)ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target).GetSpan<{internalElementType}>(), {originalElementTypeMarshaller}.ToManaged) : null");
toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.NativeArrayToManagedArray<{originalElementType}, {internalElementType}>(((ManagedArray)ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target).ToSpan<{internalElementType}>(), {originalElementTypeMarshaller}.ToManaged) : null");
toNativeContent.Append($"ManagedHandle.ToIntPtr(ManagedHandle.Alloc(ManagedArray.WrapNewArray(managed.{fieldInfo.Name}), GCHandleType.Weak))");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = ((ManagedArray)handle.Target).GetSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} ((ManagedArray)handle.Target).Free(); handle.Free(); }}");
freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = ((ManagedArray)handle.Target).GetSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} ((ManagedArray)handle.Target).Free(); handle.Free(); }}");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = ((ManagedArray)handle.Target).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} ((ManagedArray)handle.Target).Free(); handle.Free(); }}");
freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = ((ManagedArray)handle.Target).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} ((ManagedArray)handle.Target).Free(); handle.Free(); }}");
}
else if (fieldInfo.Type.GenericArgs[0].IsObjectRef)
{
// Array elements passed as GCHandles
toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>((ManagedArray)ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null");
toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedHandle.Alloc(ManagedArray.WrapNewArray(NativeInterop.ManagedArrayToGCHandleArray(managed.{fieldInfo.Name})), GCHandleType.Weak)) : IntPtr.Zero");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<IntPtr> ptrs = ((ManagedArray)handle.Target).GetSpan<IntPtr>(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} ((ManagedArray)handle.Target).Free(); handle.Free(); }}");
freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<IntPtr> ptrs = ((ManagedArray)handle.Target).GetSpan<IntPtr>(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} ((ManagedArray)handle.Target).Free(); handle.Free(); }}");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<IntPtr> ptrs = ((ManagedArray)handle.Target).ToSpan<IntPtr>(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} ((ManagedArray)handle.Target).Free(); handle.Free(); }}");
freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<IntPtr> ptrs = ((ManagedArray)handle.Target).ToSpan<IntPtr>(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} ((ManagedArray)handle.Target).Free(); handle.Free(); }}");
}
else
{
// Blittable array elements
toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? ((ManagedArray)ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target).GetArray<{originalElementType}>() : null");
toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? ((ManagedArray)ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target).ToArray<{originalElementType}>() : null");
toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedHandle.Alloc(ManagedArray.WrapNewArray(managed.{fieldInfo.Name}), GCHandleType.Weak)) : IntPtr.Zero");
freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); ((ManagedArray)handle.Target).Free(); handle.Free(); }}");
freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); ((ManagedArray)handle.Target).Free(); handle.Free(); }}");