[BUG] Module unable to resolve unmanaged DLL from runtimes directory referenced by .deps.json
kfiavde opened this issue · comments
Description
Hi there,
we try to use Prism Module Manager to load a Module from a separate directory and have trouble to load unmanaged DLLs.
We use Prism.WPF with DryIoc.
The unmanaged DLLs are referenced by Modules .deps.json file. When loading the Module, the host application's .deps.json file is used, which does not include the unmanaged DLLs. It would be cool, if the Module Loader/Initializer would use the Module's .deps.json file to resolve it's unmanaged DLL paths.
Steps to Reproduce
Here is a short description of our architecture:
- UnmanagedLibrary
- a simple C++ DLL with some exported functions
- builds for win-x64 and win-x86
- UnmanagedLibraryAsNuget
- .NET Project, just including UnmanagedLibrary DLLs for different platforms (Runtime Identifier)
- orderd by runtime id graph sub dirs (see https://natemcmaster.com/blog/2016/05/19/nuget3-rid-graph/ or https://learn.microsoft.com/de-de/dotnet/core/rid-catalog)
- pattern: runtimes/{rid}/native/*.dll
- ManagedLibrary
- Uses UnmanagedLibraryAsNuget and implements managed wrapper
- Uses [DllImport] Attibute to P/Invoke unmanaged DLL functions
- BaseLibrary
- common base for Hub application and Modules
- SomeModule
- based on BaseLibrary
- implements Prism.Modularity.IModule
- provides ManagedLibrary as singleton service via IOC
- Hub application:
- loads Modules, based on BaseLibrary
- some action will call service method from SomeModule
The first call of an unmanaged function will try to load the UnmanagedLibrary, but will not find the DLL.
This is because .NET runtime uses the applications .deps.json file
Platform with bug
WPF
Affected platforms
Windows
Did you find any workaround?
There is a workaround using a custom AssemblyLoadContext. See https://learn.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support
Here is my working implementation:
public class ModuleLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;
public ModuleLoadContext(string modulePath)
{
_resolver = new AssemblyDependencyResolver(modulePath);
}
protected override Assembly? Load(AssemblyName assemblyName)
{
// Check for already loaded assemblies from Hub domain
List<Assembly> contextAssemblies = [.. AppDomain.CurrentDomain.GetAssemblies().OrderBy(a => a.FullName)];
Assembly? assembly = contextAssemblies.FirstOrDefault(a => a.FullName == assemblyName.FullName);
if (assembly is not null)
{
return assembly;
}
// try to load from modules context (the directory, where the module dll is located)
string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string? libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}
return IntPtr.Zero;
}
}
In the App.xaml.cs we override protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
.
First we used a DirectoryModuleCatalog
to find Modules.
Now we do the Module discovery by our self by loading their DLLs in our ModuleLoadContext.
// modulePath = path to DLL file
var moduleLoadContext = new ModuleLoadContext(modulePath);
Assembly moduleAssembly = moduleLoadContext.LoadFromAssemblyPath(modulePath);
...
moduleInfo = ... // read from moduleAssembly
...
moduleCatalog.AddModule(moduleInfo);
...
moduleManager.LoadModule(moduleInfo.ModuleName);
This will use already loaded managed assemblies from Hub application, because it is e.g. necessary that they are using Prism.Modularity.IModule
from the same assembly, so that the Types are equal.
For unmanaged DLLs the Module's dependency resolver is used, which will take the Module's .deps.json file to resolve them.
Relevant log output
No response
This is not something Prism will be adding support for. Handling unmanaged assemblies is the responsibility of the developer. You did the right thing by extending Prism to handle your custom unmanaged assemblies.