我有一个使用 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);
}
我通过网络上的帖子弄清楚了这一点。基本上你需要像这样定义你自己的加载上下文(遗憾的是我不记得在哪里找到这段代码):
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;
}
}