Fix using managed arrays with Variant in dotnet7

This commit is contained in:
Wojtek Figat
2023-03-27 19:41:53 +02:00
parent 8d7225c056
commit ed13de2d5b
7 changed files with 121 additions and 84 deletions

View File

@@ -391,8 +391,8 @@ namespace FlaxEditor.Content.Import
MaxSize = managed.MaxSize,
TextureGroup = managed.TextureGroup,
Size = managed.Size,
SpriteAreas = managed.SpriteAreas?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(NativeInterop.ManagedArrayToGCHandleArray(managed.SpriteAreas))) : IntPtr.Zero,
SpriteNames = managed.SpriteNames?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(NativeInterop.ManagedArrayToGCHandleArray(managed.SpriteNames))) : IntPtr.Zero,
SpriteAreas = managed.SpriteAreas?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.SpriteAreas)) : IntPtr.Zero,
SpriteNames = managed.SpriteNames?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.SpriteNames)) : IntPtr.Zero,
};
}
internal static void Free(InternalOptionsNative unmanaged)

View File

@@ -157,39 +157,49 @@ namespace FlaxEngine
/// </summary>
public unsafe class ManagedArray
{
private ManagedHandle pinnedArrayHandle;
private IntPtr unmanagedData;
private Type elementType;
private int elementSize;
private int length;
private ManagedHandle _pinnedArrayHandle;
private IntPtr _unmanagedData;
private Type _arrayType;
private Type _elementType;
private int _elementSize;
private int _length;
public static ManagedArray WrapNewArray(Array arr) => new ManagedArray(arr);
public static ManagedArray WrapNewArray(Array arr) => new ManagedArray(arr, arr.GetType());
public static ManagedArray WrapNewArray(Array arr, Type arrayType) => new ManagedArray(arr, arrayType);
/// <summary>
/// Returns an instance of ManagedArray from shared pool.
/// </summary>
/// <remarks>
/// The resources must be released by calling FreePooled() instead of Free()-method.
/// </remarks>
/// <remarks>The resources must be released by calling FreePooled() instead of Free()-method.</remarks>
public static ManagedArray WrapPooledArray(Array arr)
{
ManagedArray managedArray = ManagedArrayPool.Get();
managedArray.WrapArray(arr);
managedArray.WrapArray(arr, arr.GetType());
return managedArray;
}
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, Type elementType)
=> new ManagedArray(ptr, length, elementType);
/// <summary>
/// Returns an instance of ManagedArray from shared pool.
/// </summary>
/// <remarks>
/// The resources must be released by calling FreePooled() instead of Free()-method.
/// </remarks>
/// <remarks>The resources must be released by calling FreePooled() instead of Free()-method.</remarks>
public static ManagedArray WrapPooledArray(Array arr, Type arrayType)
{
ManagedArray managedArray = ManagedArrayPool.Get();
managedArray.WrapArray(arr, arrayType);
return managedArray;
}
internal static ManagedArray AllocateNewArray(int length, Type arrayType, Type elementType)
=> new ManagedArray((IntPtr)NativeInterop.NativeAlloc(length, Marshal.SizeOf(elementType)), length, arrayType, elementType);
internal static ManagedArray AllocateNewArray(IntPtr ptr, int length, Type arrayType, Type elementType)
=> new ManagedArray(ptr, length, arrayType, elementType);
/// <summary>
/// Returns an instance of ManagedArray from shared pool.
/// </summary>
/// <remarks>The resources must be released by calling FreePooled() instead of Free()-method.</remarks>
public static ManagedArray AllocatePooledArray<T>(T* ptr, int length) where T : unmanaged
{
ManagedArray managedArray = ManagedArrayPool.Get();
@@ -200,67 +210,67 @@ namespace FlaxEngine
/// <summary>
/// Returns an instance of ManagedArray from shared pool.
/// </summary>
/// <remarks>
/// The resources must be released by calling FreePooled() instead of Free()-method.
/// </remarks>
/// <remarks>The resources must be released by calling FreePooled() instead of Free()-method.</remarks>
public static ManagedArray AllocatePooledArray<T>(int length) where T : unmanaged
{
ManagedArray managedArray = ManagedArrayPool.Get();
managedArray.Allocate((IntPtr)NativeInterop.NativeAlloc(length, Unsafe.SizeOf<T>()), length, typeof(T));
managedArray.Allocate((T*)NativeInterop.NativeAlloc(length, Unsafe.SizeOf<T>()), length);
return managedArray;
}
public ManagedArray(Array arr) => WrapArray(arr);
public ManagedArray(Array arr, Type elementType) => WrapArray(arr, elementType);
internal void WrapArray(Array arr)
internal void WrapArray(Array arr, Type arrayType)
{
pinnedArrayHandle = ManagedHandle.Alloc(arr, GCHandleType.Pinned);
unmanagedData = Marshal.UnsafeAddrOfPinnedArrayElement(arr, 0);
length = arr.Length;
elementType = arr.GetType().GetElementType();
elementSize = Marshal.SizeOf(elementType);
_pinnedArrayHandle = ManagedHandle.Alloc(arr, GCHandleType.Pinned);
_unmanagedData = Marshal.UnsafeAddrOfPinnedArrayElement(arr, 0);
_length = arr.Length;
_arrayType = arrayType;
_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, Type elementType)
{
unmanagedData = ptr;
this.length = length;
this.elementType = elementType;
elementSize = Marshal.SizeOf(elementType);
_unmanagedData = new IntPtr(ptr);
_length = length;
_arrayType = typeof(T).MakeArrayType();
_elementType = typeof(T);
_elementSize = Unsafe.SizeOf<T>();
}
private ManagedArray()
{
}
private ManagedArray(IntPtr ptr, int length, Type elementType) => Allocate(ptr, length, elementType);
private ManagedArray(IntPtr ptr, int length, Type arrayType, Type elementType)
{
Assert.IsTrue(arrayType.IsArray);
_unmanagedData = ptr;
_length = length;
_arrayType = arrayType;
_elementType = elementType;
_elementSize = Marshal.SizeOf(elementType);
}
~ManagedArray()
{
if (unmanagedData != IntPtr.Zero)
if (_unmanagedData != IntPtr.Zero)
Free();
}
public void Free()
{
GC.SuppressFinalize(this);
if (pinnedArrayHandle.IsAllocated)
if (_pinnedArrayHandle.IsAllocated)
{
pinnedArrayHandle.Free();
unmanagedData = IntPtr.Zero;
_pinnedArrayHandle.Free();
_unmanagedData = IntPtr.Zero;
}
if (unmanagedData != IntPtr.Zero)
if (_unmanagedData != IntPtr.Zero)
{
NativeInterop.NativeFree(unmanagedData.ToPointer());
unmanagedData = IntPtr.Zero;
NativeInterop.NativeFree(_unmanagedData.ToPointer());
_unmanagedData = IntPtr.Zero;
}
}
@@ -270,17 +280,21 @@ namespace FlaxEngine
ManagedArrayPool.Put(this);
}
internal IntPtr Pointer => unmanagedData;
internal IntPtr Pointer => _unmanagedData;
internal int Length => length;
internal int Length => _length;
internal int ElementSize => elementSize;
internal int ElementSize => _elementSize;
public Span<T> ToSpan<T>() where T : struct => new Span<T>(unmanagedData.ToPointer(), length);
internal Type ElementType => _elementType;
public T[] ToArray<T>() where T : struct => new Span<T>(unmanagedData.ToPointer(), length).ToArray();
internal Type ArrayType => _arrayType;
public Array ToArray() => ArrayCast.ToArray(new Span<byte>(unmanagedData.ToPointer(), length * elementSize), elementType);
public Span<T> ToSpan<T>() where T : struct => new Span<T>(_unmanagedData.ToPointer(), _length);
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.
@@ -1251,6 +1265,8 @@ namespace FlaxEngine
internal static IntPtr[] ManagedArrayToGCHandleArray(Array array)
{
if (array.Length == 0)
return Array.Empty<IntPtr>();
IntPtr[] pointerArray = new IntPtr[array.Length];
for (int i = 0; i < pointerArray.Length; i++)
{
@@ -1261,6 +1277,12 @@ namespace FlaxEngine
return pointerArray;
}
internal static ManagedArray ManagedArrayToGCHandleWrappedArray(Array array)
{
IntPtr[] pointerArray = ManagedArrayToGCHandleArray(array);
return ManagedArray.WrapNewArray(pointerArray, array.GetType());
}
internal static T[] NativeArrayToManagedArray<T, U>(Span<U> nativeSpan, Func<U, T> toManagedFunc)
{
T[] managedArray = new T[nativeSpan.Length];
@@ -1877,11 +1899,11 @@ namespace FlaxEngine
var marshalledType = ArrayFactory.GetMarshalledType(elementType);
ManagedArray managedArray;
if (marshalledType == elementType)
managedArray = ManagedArray.WrapNewArray(arr);
managedArray = ManagedArray.WrapNewArray(arr, type);
else if (elementType.IsValueType)
{
// Convert array of custom structures into internal native layout
managedArray = ManagedArray.AllocateNewArray(arr.Length, marshalledType);
managedArray = ManagedArray.AllocateNewArray(arr.Length, type, marshalledType);
IntPtr managedArrayPtr = managedArray.Pointer;
for (int i = 0; i < arr.Length; i++)
{
@@ -1890,7 +1912,7 @@ namespace FlaxEngine
}
}
else
managedArray = ManagedArray.WrapNewArray(ManagedArrayToGCHandleArray(arr));
managedArray = ManagedArrayToGCHandleWrappedArray(arr);
managedPtr = ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak);
}
}
@@ -2325,21 +2347,30 @@ namespace FlaxEngine
[UnmanagedCallersOnly]
internal static ManagedHandle NewArray(ManagedHandle typeHandle, long size)
{
Type type = Unsafe.As<Type>(typeHandle.Target);
Type marshalledType = ArrayFactory.GetMarshalledType(type);
Type elementType = Unsafe.As<Type>(typeHandle.Target);
Type marshalledType = ArrayFactory.GetMarshalledType(elementType);
Type arrayType = elementType.MakeArrayType();
if (marshalledType.IsValueType)
{
ManagedArray managedArray = ManagedArray.AllocateNewArray((int)size, marshalledType);
ManagedArray managedArray = ManagedArray.AllocateNewArray((int)size, arrayType, marshalledType);
return ManagedHandle.Alloc(managedArray);
}
else
{
Array arr = ArrayFactory.CreateArray(type, size);
ManagedArray managedArray = ManagedArray.WrapNewArray(arr);
Array arr = ArrayFactory.CreateArray(elementType, size);
ManagedArray managedArray = ManagedArray.WrapNewArray(arr, arrayType);
return ManagedHandle.Alloc(managedArray);
}
}
[UnmanagedCallersOnly]
internal static unsafe ManagedHandle GetArrayTypeFromElementType(ManagedHandle elementTypeHandle)
{
Type elementType = Unsafe.As<Type>(elementTypeHandle.Target);
Type classType = elementType.MakeArrayType();
return GetTypeGCHandle(classType);
}
[UnmanagedCallersOnly]
internal static unsafe IntPtr GetArrayPointer(ManagedHandle arrayHandle)
{
@@ -2392,8 +2423,10 @@ namespace FlaxEngine
[UnmanagedCallersOnly]
internal static ManagedHandle GetObjectType(ManagedHandle handle)
{
var obj = handle.Target;
object obj = handle.Target;
Type classType = obj.GetType();
if (classType == typeof(ManagedArray))
classType = ((ManagedArray)obj).ArrayType;
return GetTypeGCHandle(classType);
}
@@ -2688,15 +2721,17 @@ namespace FlaxEngine
}
[UnmanagedCallersOnly]
internal static unsafe int NativeSizeOf(ManagedHandle typeHandle, uint* align)
internal static unsafe int NativeSizeOf(ManagedHandle typeHandle)
{
Type type = Unsafe.As<Type>(typeHandle.Target);
Type nativeType = GetInternalType(type) ?? type;
if (nativeType == typeof(Version))
nativeType = typeof(VersionNative);
int size = Marshal.SizeOf(nativeType);
*align = (uint)size; // Is this correct?
int size;
if (nativeType.IsClass)
size = sizeof(IntPtr);
else
size = Marshal.SizeOf(nativeType);
return size;
}
@@ -2927,6 +2962,7 @@ namespace FlaxEngine
break;
}
case Type _ when type.IsArray:
case Type _ when type == typeof(ManagedArray):
monoType = MTypes.Array;
break;
case Type _ when type.IsValueType && !type.IsEnum && !type.IsPrimitive:

View File

@@ -88,7 +88,7 @@ namespace FlaxEngine
var elementType = typeof(TRet).GetElementType();
if (ArrayFactory.GetMarshalledType(elementType) == elementType)
return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As<Array>(returnValue)), GCHandleType.Weak);
return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(ManagedArrayToGCHandleArray(Unsafe.As<Array>(returnValue))), GCHandleType.Weak);
return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As<Array>(returnValue)), GCHandleType.Weak);
}
return ManagedHandle.ToIntPtr(returnValue, GCHandleType.Weak);
}
@@ -108,7 +108,7 @@ namespace FlaxEngine
if (returnType.IsArray && ArrayFactory.GetMarshalledType(returnType.GetElementType()) == returnType.GetElementType())
return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As<Array>(returnObject)), GCHandleType.Weak);
if (returnType.IsArray)
return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(ManagedArrayToGCHandleArray(Unsafe.As<Array>(returnObject))), GCHandleType.Weak);
return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As<Array>(returnObject)), GCHandleType.Weak);
return ManagedHandle.ToIntPtr(returnObject, GCHandleType.Weak);
}
@@ -129,7 +129,7 @@ namespace FlaxEngine
var elementType = typeof(TRet).GetElementType();
if (ArrayFactory.GetMarshalledType(elementType) == elementType)
return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As<Array>(returnValue)), GCHandleType.Weak);
return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(ManagedArrayToGCHandleArray(Unsafe.As<Array>(returnValue))), GCHandleType.Weak);
return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As<Array>(returnValue)), GCHandleType.Weak);
}
// Match Mono bindings and pass value as pointer to prevent boxing it
if (typeof(TRet) == typeof(System.Boolean))
@@ -166,7 +166,7 @@ namespace FlaxEngine
var elementType = returnType.GetElementType();
if (ArrayFactory.GetMarshalledType(elementType) == elementType)
return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(Unsafe.As<Array>(returnObject)), GCHandleType.Weak);
return ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(ManagedArrayToGCHandleArray(Unsafe.As<Array>(returnObject))), GCHandleType.Weak);
return ManagedHandle.ToIntPtr(ManagedArrayToGCHandleWrappedArray(Unsafe.As<Array>(returnObject)), GCHandleType.Weak);
}
// Match Mono bindings and pass value as pointer to prevent boxing it
if (returnType == typeof(System.Boolean))

View File

@@ -1200,9 +1200,10 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
{
// Box instance into C# object
MObject* instanceObject = MUtils::BoxVariant(instance);
const MClass* instanceObjectClass = MCore::Object::GetClass(instanceObject);
// Validate instance
if (!instanceObject || !MCore::Object::GetClass(instanceObject)->IsSubClassOf(mMethod->GetParentClass(), withInterfaces))
if (!instanceObject || !instanceObjectClass->IsSubClassOf(mMethod->GetParentClass(), withInterfaces))
{
if (!instanceObject)
LOG(Error, "Failed to call method '{0}.{1}' (args count: {2}) without object instance", String(mMethod->GetParentClass()->GetFullName()), String(mMethod->GetName()), parametersCount);
@@ -1212,7 +1213,7 @@ bool ManagedBinaryModule::InvokeMethod(void* method, const Variant& instance, Sp
}
// For value-types instance is the actual boxed object data, not te object itself
mInstance = MCore::Object::GetClass(instanceObject)->IsValueType() ? MCore::Object::Unbox(instanceObject) : instanceObject;
mInstance = instanceObjectClass->IsValueType() ? MCore::Object::Unbox(instanceObject) : instanceObject;
}
// Marshal parameters

View File

@@ -389,7 +389,8 @@ Variant MUtils::UnboxVariant(MObject* value)
case MTypes::Array:
{
void* ptr = MCore::Array::GetAddress((MArray*)value);
if (klass->GetElementClass() == MCore::TypeCache::Byte)
MClass* elementClass = klass->GetElementClass();
if (elementClass == MCore::TypeCache::Byte)
{
Variant v;
v.SetBlob(ptr, MCore::Array::GetLength((MArray*)value));
@@ -401,7 +402,6 @@ Variant MUtils::UnboxVariant(MObject* value)
auto& array = v.AsArray();
array.Resize(MCore::Array::GetLength((MArray*)value));
const StringAnsiView elementTypename(*fullname, fullname.Length() - 2);
MClass* elementClass = klass->GetElementClass();
const int32 elementSize = elementClass->GetInstanceSize();
if (elementClass->IsEnum())
{

View File

@@ -374,8 +374,9 @@ MArray* MCore::Array::New(const MClass* elementKlass, int32 length)
MClass* MCore::Array::GetClass(MClass* elementKlass)
{
MISSING_CODE("TODO: MCore::Array::GetClass"); // TODO: MCore::Object::GetClass
return nullptr;
static void* GetArrayLengthPtr = GetStaticMethodPointer(TEXT("GetArrayTypeFromElementType"));
void* classHandle = CallStaticMethod<void*, void*>(GetArrayLengthPtr, elementKlass->_handle);
return GetOrCreateClass((void*)classHandle);
}
int32 MCore::Array::GetLength(const MArray* obj)
@@ -806,9 +807,8 @@ uint32 MClass::GetInstanceSize() const
{
if (_size != 0)
return _size;
uint32 align;
static void* NativeSizeOfPtr = GetStaticMethodPointer(TEXT("NativeSizeOf"));
_size = CallStaticMethod<int, void*, uint32*>(NativeSizeOfPtr, _handle, &align);
_size = CallStaticMethod<int, void*>(NativeSizeOfPtr, _handle);
return _size;
}

View File

@@ -1541,7 +1541,7 @@ namespace Flax.Build.Bindings
{
// 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(ManagedArray.WrapNewArray(NativeInterop.ManagedArrayToGCHandleArray(managed.{fieldInfo.Name})), GCHandleType.Weak) : IntPtr.Zero");
toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(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).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(); }}");
}