加载 MEF 插件的本机依赖项

问题描述 投票:0回答:1

我有一个使用 MEF(System.Composition)作为其插件架构的 .NET 8.0 应用程序。 大多数插件都能正常工作,但有一些插件会在运行时抛出异常。这两个插件有一个共同点,它们依赖于使用“运行时”文件夹中的本机 dll 的库。一个有 Microsoft.Web.WebView2,另一个有 LiveCharts2,它使用 Skia。他们有看起来像这样的“运行时”树(例如,对于 livecharts 插件):

runtimes
|-- osx
|   `-- native
|       |-- libHarfBuzzSharp.dylib
|       `-- libSkiaSharp.dylib
|-- win-arm64
|   `-- native
|       |-- libHarfBuzzSharp.dll
|       `-- libSkiaSharp.dll
|-- win-x64
|   `-- native
|       |-- libHarfBuzzSharp.dll
|       `-- libSkiaSharp.dll
`-- win-x86
    `-- native
        |-- libHarfBuzzSharp.dll
        `-- libSkiaSharp.dll

webview 插件具有相同的树,只是具有不同的 DLL。 Runtimes 文件夹是项目的构建目录,位于其 dll 旁边。

我可以通过将依赖项移至主机应用程序(即 webview2 和 skia)来让该系统工作,但这显然并不理想,因为任何想要使用本机库的未来插件都必须将其依赖项添加到主机.

如果我的主机应用程序中没有 Skia,则 livecharts 插件将抛出:

DllNotFoundException: Unable to load DLL 'libSkiaSharp' or one of its dependencies: The specified module could not be found. (0x8007007E)

这是我的插件加载代码:

    private void Init(IEnumerable<Assembly> assemblies)
    {
        _pluginManager.ImportsSatisfied += OnImportsSatisfied;

        var configuration = new ContainerConfiguration()
            .WithAssemblies(assemblies);

        try
        {
            using (CompositionHost host = configuration.CreateContainer())
            {
                host.SatisfyImports(_pluginManager);
            }
        }
        catch (ReflectionTypeLoadException ex)
        {
            Log.Error("Could not load extension pluginNames: {0}\nLoader exceptions:{1} ", ex, ex.LoaderExceptions);
        }
        catch (Exception ex)
        {
            Log.Error("Could not load extension pluginNames: {0}", ex);
        }
    }

其中

assemblies
是加载的程序集列表:

    private static IEnumerable<Assembly> GetAssembliesFromNames(IEnumerable<string> pluginNames, string pluginDirectory)
    {
        List<Assembly> assemblies = new();
        foreach (string pluginName in pluginNames)
        {
            try
            {
                assemblies.Add(GetAssemblyForPluginByName(pluginName, pluginDirectory));
            }
            catch (Exception ex)
            {
                Log.Error("Could not load plugin assembly {0}: {1}", pluginName, ex);
            }
        }
        return assemblies;
    }
    public static Assembly GetAssemblyForPluginByName(string name, string workingDirectory)
    {
        string pluginName = Path.GetFileName(name);
        string pluginFolderFilePath = Path.Combine(workingDirectory, pluginName);
        string pluginDllPath = Path.Combine(pluginFolderFilePath, pluginName + ".dll");
        if (Directory.Exists(pluginFolderFilePath) && File.Exists(pluginDllPath))
        {
            return Assembly.LoadFrom(pluginDllPath);
        }

        throw new FileNotFoundException(name);
    }
c# dllimport mef .net-8.0 system.composition
1个回答
0
投票

我通过网络上的帖子弄清楚了这一点。基本上你需要像这样定义你自己的加载上下文(遗憾的是我不记得在哪里找到这段代码):

    using System.Reflection;
using System.Runtime.Loader;

namespace MonitoringDisplayTv.Plugins;

public class PluginLoadContext : AssemblyLoadContext
{
    private readonly AssemblyDependencyResolver _resolver;
    private readonly IEnumerable<string> _sharedAssemblyNames;

    public PluginLoadContext(string pluginPath) : this(pluginPath, [])
    {
    }

    public PluginLoadContext(string pluginPath, IEnumerable<string> assemblies) : base(true)
    {
        _resolver = new AssemblyDependencyResolver(pluginPath);

        _sharedAssemblyNames = assemblies;
    }

    protected override Assembly? Load(AssemblyName assemblyName)
    {
        if (assemblyName.Name == null)
        {
            return null;
        }

        foreach (string name in _sharedAssemblyNames)
        {
            if (name == assemblyName.Name)
            {
                return Default.LoadFromAssemblyName(assemblyName);
            }
        }


        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;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.