From 759a9bd36518e0c337e458fa634be2d4ca88078c Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Sat, 24 Dec 2022 03:13:40 +0200 Subject: [PATCH] Fix native library resolver not working after hot-reload --- Source/Engine/Engine/NativeInterop.cs | 43 +++++++++++++++----- Source/Engine/Scripting/DotNet/CoreCLR.cpp | 6 +++ Source/Engine/Scripting/DotNet/CoreCLR.h | 2 + Source/Engine/Scripting/ManagedCLR/MCore.cpp | 5 ++- Source/Engine/Scripting/Scripting.cpp | 10 +++++ 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index b2d30875f..d58bfa4e6 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -847,27 +847,27 @@ namespace FlaxEngine private static Dictionary classAttributesCacheCollectible = new(); private static Dictionary assemblyHandles = new Dictionary(); - private static string hostExecutable; - private static IntPtr hostExecutableHandle = IntPtr.Zero; + private static Dictionary loadedNativeLibraries = new Dictionary(); + private static Dictionary nativeLibraryPaths = new Dictionary(); + private static Dictionary assemblyOwnedNativeLibraries = new Dictionary(); private static AssemblyLoadContext scriptingAssemblyLoadContext; [System.Diagnostics.DebuggerStepThrough] - private static IntPtr InternalDllResolver(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath) + private static IntPtr NativeLibraryImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath) { - if (libraryName == "FlaxEngine") + if (!loadedNativeLibraries.TryGetValue(libraryName, out IntPtr nativeLibrary)) { - if (hostExecutableHandle == IntPtr.Zero) - hostExecutableHandle = NativeLibrary.Load(hostExecutable, assembly, dllImportSearchPath); - return hostExecutableHandle; + nativeLibrary = NativeLibrary.Load(nativeLibraryPaths[libraryName], assembly, dllImportSearchPath); + loadedNativeLibraries.Add(libraryName, nativeLibrary); + assemblyOwnedNativeLibraries.Add(assembly, libraryName); } - return IntPtr.Zero; + return nativeLibrary; } [UnmanagedCallersOnly] - internal static unsafe void Init(IntPtr hostExecutableName) + internal static unsafe void Init() { - hostExecutable = Marshal.PtrToStringUni(hostExecutableName); - NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), InternalDllResolver); + NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), NativeLibraryImportResolver); // Change default culture to match with Mono runtime default culture CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; @@ -886,6 +886,15 @@ namespace FlaxEngine { } + [UnmanagedCallersOnly] + internal static void RegisterNativeLibrary(IntPtr moduleName_, IntPtr modulePath_) + { + string moduleName = Marshal.PtrToStringAnsi(moduleName_); + string modulePath = Marshal.PtrToStringAnsi(modulePath_); + + nativeLibraryPaths[moduleName] = modulePath; + } + internal static T[] GCHandleArrayToManagedArray(ManagedArray ptrArray) { Span span = ptrArray.GetSpan(); @@ -2145,6 +2154,7 @@ namespace FlaxEngine string assemblyPath = Marshal.PtrToStringAnsi(assemblyPath_); Assembly assembly = scriptingAssemblyLoadContext.LoadFromAssemblyPath(assemblyPath); + NativeLibrary.SetDllImportResolver(assembly, NativeLibraryImportResolver); *assemblyName = Marshal.StringToCoTaskMemAnsi(assembly.GetName().Name); *assemblyFullName = Marshal.StringToCoTaskMemAnsi(assembly.FullName); @@ -2172,6 +2182,7 @@ namespace FlaxEngine using MemoryStream stream = new MemoryStream(raw); Assembly assembly = scriptingAssemblyLoadContext.LoadFromStream(stream); + NativeLibrary.SetDllImportResolver(assembly, NativeLibraryImportResolver); // Assemblies loaded via streams have no Location: https://github.com/dotnet/runtime/issues/12822 AssemblyLocations.Add(assembly.FullName, assemblyPath); @@ -2231,6 +2242,16 @@ namespace FlaxEngine pair.Value.Free(); classAttributesCacheCollectible.Clear(); + // Unload native library handles associated for this assembly + string nativeLibraryName = assemblyOwnedNativeLibraries.GetValueOrDefault(assembly); + if (nativeLibraryName != null && loadedNativeLibraries.TryGetValue(nativeLibraryName, out IntPtr nativeLibrary)) + { + NativeLibrary.Free(nativeLibrary); + loadedNativeLibraries.Remove(nativeLibraryName); + } + if (nativeLibraryName != null) + nativeLibraryPaths.Remove(nativeLibraryName); + // Unload the ALC bool unloading = true; scriptingAssemblyLoadContext.Unloading += (alc) => { unloading = false; }; diff --git a/Source/Engine/Scripting/DotNet/CoreCLR.cpp b/Source/Engine/Scripting/DotNet/CoreCLR.cpp index fe3983b0f..5026dc805 100644 --- a/Source/Engine/Scripting/DotNet/CoreCLR.cpp +++ b/Source/Engine/Scripting/DotNet/CoreCLR.cpp @@ -121,6 +121,12 @@ void* CoreCLR::GetStaticMethodPointer(const String& methodName) return fun; } +void CoreCLR::RegisterNativeLibrary(const char* moduleName, const char* modulePath) +{ + static void* RegisterNativeLibraryPtr = CoreCLR::GetStaticMethodPointer(TEXT("RegisterNativeLibrary")); + CoreCLR::CallStaticMethod(RegisterNativeLibraryPtr, moduleName, modulePath); +} + void* CoreCLR::Allocate(int size) { #if PLATFORM_WINDOWS diff --git a/Source/Engine/Scripting/DotNet/CoreCLR.h b/Source/Engine/Scripting/DotNet/CoreCLR.h index bd6c7ecb3..1d1bec932 100644 --- a/Source/Engine/Scripting/DotNet/CoreCLR.h +++ b/Source/Engine/Scripting/DotNet/CoreCLR.h @@ -47,6 +47,8 @@ public: return ((fun)methodPtr)(args...); } + static void RegisterNativeLibrary(const char* moduleName, const char* modulePath); + static const char* GetClassFullname(void* klass); static void* Allocate(int size); static void Free(void* ptr); diff --git a/Source/Engine/Scripting/ManagedCLR/MCore.cpp b/Source/Engine/Scripting/ManagedCLR/MCore.cpp index f1b7763cb..df5c0a081 100644 --- a/Source/Engine/Scripting/ManagedCLR/MCore.cpp +++ b/Source/Engine/Scripting/ManagedCLR/MCore.cpp @@ -135,8 +135,9 @@ bool MCore::LoadEngine() return false; // Prepare managed side - const String hostExecutable = Platform::GetExecutableFilePath(); - CoreCLR::CallStaticMethodByName(TEXT("Init"), hostExecutable.Get()); + const StringAnsi hostExecutable(Platform::GetExecutableFilePath()); + CoreCLR::CallStaticMethodByName(TEXT("Init")); + CoreCLR::RegisterNativeLibrary("FlaxEngine", hostExecutable.Get()); MRootDomain = New("Root"); MDomains.Add(MRootDomain); diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index c44f71823..d001ce46c 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -34,6 +34,9 @@ #include #include #endif +#if USE_NETCORE +#include "DotNet/CoreCLR.h" +#endif extern void registerFlaxEngineInternalCalls(); @@ -408,6 +411,13 @@ bool Scripting::LoadBinaryModules(const String& path, const String& projectFolde LOG(Error, "Failed to load C# assembly '{0}' for binary module {1}.", managedPath, name); return true; } +#if USE_NETCORE + // Provide new path of hot-reloaded native library path for managed DllImport + if (nativePath.HasChars()) + { + CoreCLR::RegisterNativeLibrary(nameAnsi.Get(), StringAnsi(nativePath).Get()); + } +#endif } #endif