natemcmaster / DotNetCorePlugins

.NET Core library for dynamically loading code

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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:

  1. Add using var dbConnection = new SqliteConnection("Data Source=db.sqlite"); to the InfoDisplayer in the hot-reload sample
  2. Run samples/hot-reload/run.ps1|sh
  3. 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.

NativeLibrary.SetDllImportResolver(Assembly, DllImportResolver) looks promising. However, havn't been able to solve the SQLite scenario using it yet. None of the managed callbacks are invoked...

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).