diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 4614a78c1..34daf9fcf 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -201,8 +201,7 @@ namespace FlaxEngine /// /// Wrapper for managed arrays that needs to be pinned. /// - [StructLayout(LayoutKind.Sequential)] - internal struct ManagedArray + internal class ManagedArray { internal Array array = null; internal GCHandle handle; @@ -215,6 +214,12 @@ namespace FlaxEngine array = arr; } + ~ManagedArray() + { + if (array != null) + Release(); + } + internal void Release() { handle.Free(); @@ -239,11 +244,15 @@ namespace FlaxEngine internal static class ManagedString { + internal static GCHandle EmptyStringHandle = GCHandle.Alloc(string.Empty); + [System.Diagnostics.DebuggerStepThrough] internal static unsafe IntPtr ToNative(string str) { if (str == null) return IntPtr.Zero; + else if (str == string.Empty) + return GCHandle.ToIntPtr(EmptyStringHandle); #if false // HACK: Pin the string and pass the address to it including the length, no marshalling required GCHandle handle = GCHandle.Alloc(str, GCHandleType.Pinned); @@ -258,7 +267,7 @@ namespace FlaxEngine // We assume the string content is copied in native side before GC frees it. IntPtr ptr = (Unsafe.Read(Unsafe.AsPointer(ref str)) + sizeof(int) * 2); #endif - IntPtr ptr = GCHandle.ToIntPtr(GCHandle.Alloc(str)); + IntPtr ptr = GCHandle.ToIntPtr(GCHandle.Alloc(str, GCHandleType.Weak)); return ptr; } @@ -277,7 +286,11 @@ namespace FlaxEngine if (ptr == IntPtr.Zero) return; - GCHandle.FromIntPtr(ptr).Free(); + GCHandle handle = GCHandle.FromIntPtr(ptr); + if (handle == EmptyStringHandle) + return; + + handle.Free(); } } @@ -450,11 +463,160 @@ namespace FlaxEngine } } - [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.Default, typeof(ArrayMarshaller<,>))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ManagedToUnmanagedIn, typeof(ArrayMarshaller<,>.ManagedToNative))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.UnmanagedToManagedOut, typeof(ArrayMarshaller<,>.ManagedToNative))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ElementIn, typeof(ArrayMarshaller<,>.ManagedToNative))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ManagedToUnmanagedOut, typeof(ArrayMarshaller<,>.NativeToManaged))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.UnmanagedToManagedIn, typeof(ArrayMarshaller<,>.NativeToManaged))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ElementOut, typeof(ArrayMarshaller<,>.NativeToManaged))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ManagedToUnmanagedRef, typeof(ArrayMarshaller<,>.Bidirectional))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.UnmanagedToManagedRef, typeof(ArrayMarshaller<,>.Bidirectional))] + [CustomMarshaller(typeof(CustomMarshallerAttribute.GenericPlaceholder[]), MarshalMode.ElementRef, typeof(ArrayMarshaller<,>))] [ContiguousCollectionMarshaller] internal static unsafe class ArrayMarshaller where TUnmanagedElement : unmanaged { + public static class NativeToManaged + { + internal static T[]? AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements) + { + if (unmanaged is null) + return null; + + ManagedArray array = (ManagedArray)GCHandle.FromIntPtr(new IntPtr(unmanaged)).Target; + return new T[numElements]; + } + + internal static Span GetManagedValuesDestination(T[]? managed) + => managed; + + internal static ReadOnlySpan GetUnmanagedValuesSource(TUnmanagedElement* unmanagedValue, int numElements) + { + if (unmanagedValue == null) + return ReadOnlySpan.Empty; + + ManagedArray array = (ManagedArray)GCHandle.FromIntPtr(new IntPtr(unmanagedValue)).Target; + return new ReadOnlySpan(array.array as TUnmanagedElement[]); + } + + internal static void Free(TUnmanagedElement* unmanaged) + { + if (unmanaged == null) + return; + + GCHandle handle = GCHandle.FromIntPtr(new IntPtr(unmanaged)); + ((ManagedArray)handle.Target).Release(); + handle.Free(); + } + + internal static Span GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements) + { + if (unmanaged == null) + return Span.Empty; + + ManagedArray array = (ManagedArray)GCHandle.FromIntPtr(new IntPtr(unmanaged)).Target; + return new Span(array.array as TUnmanagedElement[]); + } + } + public static class ManagedToNative + { + internal static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[]? managed, out int numElements) + { + if (managed is null) + { + numElements = 0; + return null; + } + + numElements = managed.Length; + + ManagedArray array = ManagedArray.Get(new TUnmanagedElement[numElements]); + var ptr = GCHandle.ToIntPtr(GCHandle.Alloc(array)); + + return (TUnmanagedElement*)ptr; + } + + internal static ReadOnlySpan GetManagedValuesSource(T[]? managed) + => managed; + + internal static Span GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements) + { + if (unmanaged == null) + return Span.Empty; + + ManagedArray array = (ManagedArray)GCHandle.FromIntPtr(new IntPtr(unmanaged)).Target; + return new Span(array.array as TUnmanagedElement[]); + } + + internal static void Free(TUnmanagedElement* unmanaged) + { + if (unmanaged == null) + return; + + GCHandle handle = GCHandle.FromIntPtr(new IntPtr(unmanaged)); + ((ManagedArray)handle.Target).Release(); + handle.Free(); + } + } + public struct Bidirectional + { + T[] managedArray; + ManagedArray unmanagedArray; + GCHandle handle; + + public void FromManaged(T[]? managed) + { + if (managed == null) + return; + + managedArray = managed; + unmanagedArray = ManagedArray.Get(new TUnmanagedElement[managed.Length]); + handle = GCHandle.Alloc(unmanagedArray); + } + + public ReadOnlySpan GetManagedValuesSource() + => managedArray; + + public Span GetUnmanagedValuesDestination() + { + if (unmanagedArray == null) + return Span.Empty; + + return new Span(unmanagedArray.array as TUnmanagedElement[]); + } + + public TUnmanagedElement* ToUnmanaged() + { + return (TUnmanagedElement*)GCHandle.ToIntPtr(handle); + } + + public void FromUnmanaged(TUnmanagedElement* value) + { + ManagedArray arr = (ManagedArray)GCHandle.FromIntPtr(new IntPtr(value)).Target; + managedArray = new T[arr.Length]; + } + + public ReadOnlySpan GetUnmanagedValuesSource(int numElements) + { + if (unmanagedArray == null) + return ReadOnlySpan.Empty; + + return new ReadOnlySpan(unmanagedArray.array as TUnmanagedElement[]); + } + + public Span GetManagedValuesDestination(int numElements) + => managedArray; + + public T[] ToManaged() + => managedArray; + + public void Free() + { + unmanagedArray.Release(); + handle.Free(); + } + } + internal static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[]? managed, out int numElements) { if (managed is null) @@ -510,7 +672,7 @@ namespace FlaxEngine return; GCHandle handle = GCHandle.FromIntPtr(new IntPtr(unmanaged)); - Unsafe.Unbox(handle.Target).Release(); + ((ManagedArray)handle.Target).Release(); handle.Free(); } @@ -584,7 +746,7 @@ namespace FlaxEngine internal static IntPtr ToNative(string managed) => ManagedString.ToNative(managed); } -#endregion + #endregion /// /// Provides a Mono-like API for native code to access managed runtime. @@ -610,6 +772,7 @@ namespace FlaxEngine private static Dictionary typeHandleCacheCollectible = new Dictionary(); private static List fieldHandleCache = new(); private static List fieldHandleCacheCollectible = new(); + private static Dictionary classAttributesCacheCollectible = new(); private static Dictionary assemblyHandles = new Dictionary(); private static string hostExecutable; @@ -640,6 +803,11 @@ namespace FlaxEngine DelegateHelpers.Init(); } + [UnmanagedCallersOnly] + internal static unsafe void Exit() + { + } + internal static T[] GCHandleArrayToManagedArray(ManagedArray array) { IntPtr[] ptrArray = (IntPtr[])array.array; @@ -1172,7 +1340,11 @@ namespace FlaxEngine { IntPtr ptr = IntPtr.Add(new IntPtr(arr), Unsafe.SizeOf() * i); - GCHandle attributeHandle = GCHandle.Alloc(attributeValues[i]); + if (!classAttributesCacheCollectible.TryGetValue(attributeValues[i], out GCHandle attributeHandle)) + { + attributeHandle = GCHandle.Alloc(attributeValues[i]); + classAttributesCacheCollectible.Add(attributeValues[i], attributeHandle); + } GCHandle attributeTypeHandle = GetOrAddTypeGCHandle(attributeTypes[i]); ClassAttribute classAttribute = new ClassAttribute() @@ -1315,29 +1487,25 @@ namespace FlaxEngine [UnmanagedCallersOnly] internal static IntPtr GetStringEmpty() { - string str = ""; - return GCHandle.ToIntPtr(GCHandle.Alloc(str)); + return ManagedString.ToNative(string.Empty); } [UnmanagedCallersOnly] internal static IntPtr NewStringUTF16(char* text, int length) { - string str = new string(new ReadOnlySpan(text, length)); - return GCHandle.ToIntPtr(GCHandle.Alloc(str)); + return ManagedString.ToNative(new string(new ReadOnlySpan(text, length))); } [UnmanagedCallersOnly] internal static IntPtr NewString(sbyte* text) { - string str = new string(text); - return GCHandle.ToIntPtr(GCHandle.Alloc(str)); + return ManagedString.ToNative(new string(text)); } [UnmanagedCallersOnly] internal static IntPtr NewStringLength(sbyte* text, int length) { - string str = new string(text, 0, length); - return GCHandle.ToIntPtr(GCHandle.Alloc(str)); + return ManagedString.ToNative(new string(text, 0, length)); } /// @@ -1639,6 +1807,10 @@ namespace FlaxEngine handle.Free(); fieldHandleCacheCollectible.Clear(); + foreach (var pair in classAttributesCacheCollectible) + pair.Value.Free(); + classAttributesCacheCollectible.Clear(); + // Unload the ALC bool unloading = true; scriptingAssemblyLoadContext.Unloading += (alc) => { unloading = false; }; diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp index 6ee678c7a..cc0aca74f 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.cpp @@ -122,33 +122,35 @@ bool MCore::LoadEngine() const String csharpLibraryPath = Globals::BinariesFolder / TEXT("FlaxEngine.CSharp.dll"); const String csharpRuntimeConfigPath = Globals::BinariesFolder / TEXT("FlaxEngine.CSharp.runtimeconfig.json"); if (!FileSystem::FileExists(csharpLibraryPath)) - LOG(Fatal, "LoadHostfxr failed"); + LOG(Fatal, "Failed to initialize managed runtime, FlaxEngine.CSharp.dll is missing."); if (!FileSystem::FileExists(csharpRuntimeConfigPath)) - LOG(Fatal, "LoadHostfxr failed"); + LOG(Fatal, "Failed to initialize managed runtime, FlaxEngine.CSharp.runtimeconfig.json is missing."); // Locate hostfxr and load it if (!CoreCLR::LoadHostfxr(csharpLibraryPath)) - LOG(Fatal, "LoadHostfxr failed"); + return false; // Initialize hosting component if (!CoreCLR::InitHostfxr(csharpRuntimeConfigPath, csharpLibraryPath)) - LOG(Fatal, "LoadAssembly failed"); + return false; + // Prepare managed side const String hostExecutable = Platform::GetExecutableFilePath(); CoreCLR::CallStaticMethodInternal(TEXT("Init"), hostExecutable.Get()); MRootDomain = New("Root"); MDomains.Add(MRootDomain); - /*char* buildInfo = mono_get_runtime_build_info(); - LOG(Info, "Managed runtime version: {0} (.NET)", String(buildInfo)); - mono_free(buildInfo);*/ + char* buildInfo = mono_get_runtime_build_info(); + LOG(Info, ".NET runtime version: {0}", String(buildInfo)); + mono_free(buildInfo); return false; } void MCore::UnloadEngine() { + CoreCLR::CallStaticMethodInternal(TEXT("Exit")); MDomains.ClearDelete(); MRootDomain = nullptr; } @@ -594,7 +596,7 @@ bool MCore::LoadEngine() // Info char* buildInfo = mono_get_runtime_build_info(); - LOG(Info, "Managed runtime version: {0} (Mono)", String(buildInfo)); + LOG(Info, "Mono runtime version: {0}", String(buildInfo)); mono_free(buildInfo); return false; diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index d63140693..0dd46fe81 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -241,11 +241,12 @@ bool ScriptingObject::CreateManaged() return true; // Prevent from object GC destruction - auto handle = MUtils::NewGCHandle(managedInstance, false); #if USE_NETCORE + auto handle = (gchandle)managedInstance; auto oldHandle = Platform::InterlockedCompareExchange((int64*)&_gcHandle, *(int64*)&handle, 0); if (*(uint64*)&oldHandle != 0) #else + auto handle = MUtils::NewGCHandle(managedInstance, false); auto oldHandle = Platform::InterlockedCompareExchange((int32*)&_gcHandle, *(int32*)&handle, 0); if (*(uint32*)&oldHandle != 0) #endif @@ -457,11 +458,12 @@ bool ManagedScriptingObject::CreateManaged() return true; // Cache the GC handle to the object (used to track the target object because it can be moved in a memory) - auto handle = MUtils::NewGCHandleWeakref(managedInstance, false); #if USE_NETCORE + auto handle = (gchandle)managedInstance; auto oldHandle = Platform::InterlockedCompareExchange((int64*)&_gcHandle, *(int64*)&handle, 0); if (*(uint64*)&oldHandle != 0) #else + auto handle = MUtils::NewGCHandleWeakref(managedInstance, false); auto oldHandle = Platform::InterlockedCompareExchange((int32*)&_gcHandle, *(int32*)&handle, 0); if (*(uint32*)&oldHandle != 0) #endif @@ -642,12 +644,20 @@ public: if (auto* managedScriptingObject = dynamic_cast(obj)) { // Managed +#if USE_NETCORE + managedScriptingObject->_gcHandle = (gchandle)managedInstance; +#else managedScriptingObject->_gcHandle = MUtils::NewGCHandleWeakref(managedInstance, false); +#endif } else { // Persistent +#if USE_NETCORE + obj->_gcHandle = (gchandle)managedInstance; +#else obj->_gcHandle = MUtils::NewGCHandle(managedInstance, false); +#endif } MClass* monoClass = obj->GetClass(); diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 32b8fa7ab..77f217ea8 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -1451,24 +1451,32 @@ namespace Flax.Build.Bindings { if (internalType) { + // Marshal blittable array elements back to original non-blittable elements string originalElementType = originalType.Substring(0, originalType.Length - 2); string originalElementTypeMarshaller = originalElementType + "Marshaller"; - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.NativeArrayToManagedArray<{originalElementType}, {originalElementTypeMarshaller}.{originalElementType}Internal>(((ManagedArray)GCHandle.FromIntPtr(managed.{fieldInfo.Name}).Target).array as {originalElementTypeMarshaller}.{originalElementType}Internal[], {originalElementTypeMarshaller}.ToManaged) : null"); + string internalElementType = $"{originalElementTypeMarshaller}.{originalElementType}Internal"; + toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.NativeArrayToManagedArray<{originalElementType}, {internalElementType}>(((ManagedArray)GCHandle.FromIntPtr(managed.{fieldInfo.Name}).Target).array as {internalElementType}[], {originalElementTypeMarshaller}.ToManaged) : null"); toNativeContent.Append($"GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(managed.{fieldInfo.Name}), GCHandleType.Weak))"); + freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); {internalElementType}[] values = ({internalElementType}[])(((ManagedArray)handle.Target).array); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); + freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); {internalElementType}[] values = ({internalElementType}[])(((ManagedArray)handle.Target).array); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); } else if (fieldInfo.Type.GenericArgs[0].IsObjectRef) { + // Array elements passed as GCHandles string originalElementType = originalType.Substring(0, originalType.Length - 2); toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>((ManagedArray)GCHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(NativeInterop.ManagedArrayToGCHandleArray(managed.{fieldInfo.Name})), GCHandleType.Weak)) : IntPtr.Zero"); + freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); IntPtr[] ptrs = (IntPtr[])(((ManagedArray)handle.Target).array); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ GCHandle.FromIntPtr(ptr).Free(); }} }} ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); + freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); IntPtr[] ptrs = (IntPtr[])(((ManagedArray)handle.Target).array); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ GCHandle.FromIntPtr(ptr).Free(); }} }} ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); } else { + // Blittable array elements toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? ({originalType})(((ManagedArray)GCHandle.FromIntPtr(managed.{fieldInfo.Name}).Target).array) : null"); toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? GCHandle.ToIntPtr(GCHandle.Alloc(ManagedArray.Get(managed.{fieldInfo.Name}), GCHandleType.Weak)) : IntPtr.Zero"); + freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); + freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); ((ManagedArray)handle.Target).Release(); handle.Free(); }}"); } - freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Unsafe.Unbox(handle.Target).Release(); handle.Free(); }}"); - freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ GCHandle handle = GCHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Unsafe.Unbox(handle.Target).Release(); handle.Free(); }}"); } else if (fieldInfo.Type.Type == "Version") { @@ -1542,7 +1550,7 @@ namespace Flax.Build.Bindings contents.Append(indent2).AppendLine($"public {structureInfo.Name}Internal ToUnmanaged() {{ unmanaged = {marshallerName}.ToNative(managed); return unmanaged.Value; }}"); //contents.Append(indent2).AppendLine($"public void FromUnmanaged({structureInfo.Name}Internal unmanaged) {{ {marshallerName}.Free(this.unmanaged.Value); this.unmanaged = unmanaged; }}"); contents.Append(indent2).AppendLine($"public void FromUnmanaged({structureInfo.Name}Internal unmanaged) {{ this.unmanaged = unmanaged; }}"); - contents.Append(indent2).AppendLine($"public {structureInfo.Name} ToManaged() {{ managed = {marshallerName}.ToManaged(unmanaged.Value); unmanaged = null; return managed; }}"); + contents.Append(indent2).AppendLine($"public {structureInfo.Name} ToManaged() {{ managed = {marshallerName}.ToManaged(unmanaged.Value); return managed; }}"); contents.Append(indent2).AppendLine($"public void Free() {{ if (unmanaged.HasValue) {{ NativeToManaged.Free(unmanaged.Value); unmanaged = null; }} }}"); contents.Append(indent).AppendLine("}");