Hot Reloading and NativeLibrary.Load(String, Assembly, DllImportSearchPath?)
KatoStoelen opened this issue · comments
Describe the bug
Not sure if this is a bug or something this library cannot support. I was trying to reproduce #146 using the hot-reload sample. There we use Microsoft.Data.Sqlite to test shadow copying and loading of sni.dll. When expandig the sample by creating an instance of SqliteConnection
, there is a lot more happening under the hood. Particulary in SQLitePCL.raw which uses system.runtime.interopservices.nativelibrary.load to load native libraries. This API does not invoke AssemblyLoadContext.LoadUnmanagedDll
. Hence, this library does not control how those native libraries are loaded, and cannot perform any shadow copying etc. That is, if my understanding is correct.
To Reproduce
Steps to reproduce the behavior:
- Add
using var dbConnection = new SqliteConnection("Data Source=db.sqlite");
to theInfoDisplayer
in the hot-reload sample - Run samples/hot-reload/run.ps1|sh
- See error
System.DllNotFoundException: Unable to load DLL 'e_sqlite3' or one of its dependencies: The specified module could not be found. (0x8007007E)
Expected behavior
Would be nice if there is a solution to this issue, but I can understand that there might not be.
Additional context
Full stack trace:
Unhandled exception. System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
---> System.TypeInitializationException: The type initializer for 'Microsoft.Data.Sqlite.SqliteConnection' threw an exception.
---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
---> System.DllNotFoundException: Unable to load DLL 'e_sqlite3' or one of its dependencies: The specified module could not be found. (0x8007007E)
at System.Runtime.InteropServices.NativeLibrary.LoadByName(String libraryName, QCallAssembly callingAssembly, Boolean hasDllImportSearchPathFlag, UInt32 dllImportSearchPathFlag, Boolean throwOnError)
at System.Runtime.InteropServices.NativeLibrary.LoadLibraryByName(String libraryName, Assembly assembly, Nullable`1 searchPath, Boolean throwOnError)
at System.Runtime.InteropServices.NativeLibrary.Load(String libraryName, Assembly assembly, Nullable`1 searchPath)
at SQLitePCL.Batteries_V2.MakeDynamic(String name, Int32 flags)
at SQLitePCL.Batteries_V2.DoDynamic_cdecl(String name, Int32 flags)
--- End of inner exception stack trace ---
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Microsoft.Data.Sqlite.Utilities.BundleInitializer.Initialize()
at Microsoft.Data.Sqlite.SqliteConnection..cctor()
--- End of inner exception stack trace ---
at Microsoft.Data.Sqlite.SqliteConnection..ctor(String connectionString)
at TimestampedPlugin.InfoDisplayer.Print()
--- End of inner exception stack trace ---
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
at HostApp.Program.InvokePlugin(PluginLoader loader) in C:\Source\private\DotNetCorePlugins\samples\hot-reload\HotReloadApp\Program.cs:line 39
at HostApp.Program.Main(String[] args) in C:\Source\private\DotNetCorePlugins\samples\hot-reload\HotReloadApp\Program.cs:line 22
at HostApp.Program.<Main>(String[] args)
Well, what did work in this specific scenario was to explicitly load e_sqlite3.dll
by calling LoadUnmanagedDll("e_sqlite3")
during initialization of ManagedLoadContext
. Not exactly sure why that worked. Probably due to my limited knowledge of how loading of managed/unmanaged DLLs actually works.
My first thought was that e_sqlite3.dll
could be found if it existed within the shadow copy directory. However, I've been unable to confirm it.
Anyways. It seems like this would be solvable with an API to "pre-load" specific unmanaged DLLs. ManagedLoadContext
would then probably have to include some clean-up logic to free the pre-loaded handles.
The public API suggestion above was abandoned. Looking for solutions where the host does not have to care about the dependencies of plugins, instead.
None of the managed callbacks are invoked...NativeLibrary.SetDllImportResolver(Assembly, DllImportResolver)
looks promising. However, havn't been able to solve the SQLite scenario using it yet.
On a positive note, seems like the behavior of NativeLibrary.Load(String, Assembly, DllImportSearchPath?)
is changed in .NET 5 (dotnet/runtime#13819), and will call AssemblyLoadContext.LoadUnmanagedDll(String)
going forward.
Closing this. Not possible to fix for .NET Core.
Also, this issue is a duplicate of #84 (sorry about that).