I have a .NET 8.0 application using MEF (System.Composition) as its plugin architecture. Most of the plugins work properly however there are a couple that will throw exceptions at runtime. Both of these plugins have one thing in common, they rely on libraries that use native dlls from a “runtime” folder. One has Microsoft.Web.WebView2 and the other has LiveCharts2, which uses Skia. They have “runtime” trees that look like this (e.g. for the livecharts plugin):
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
The webview plugin has the same tree, just with different DLLs. The runtimes folder is the project’s build directory next to its dll.
I can get this system to work by moving the dependencies to the host application (i.e. webview2 and skia), but that is obviously not ideal since any future plugin that wants to use a native library would have to have its dependencies added to the host.
If I do not have Skia in the host application the livecharts plugin will throw:
DllNotFoundException: Unable to load DLL 'libSkiaSharp' or one of its dependencies: The specified module could not be found. (0x8007007E)
Here is my plugin loading code:
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);
}
}
Where assemblies
is a list of assemblies loaded with:
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);
}
Ryan Jeffrey is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.