PrismLibrary / Prism

Prism is a framework for building loosely coupled, maintainable, and testable XAML applications in WPF, Xamarin Forms, and Uno / Win UI Applications..

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[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
  • 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.