using System.Reflection;
using AcDream.Plugin.Abstractions;
namespace AcDream.Core.Plugins;
public static class PluginLoader
{
///
/// Load a plugin DLL from into a collectible
/// , find the first type
/// implementing , instantiate it, and call its
/// with the supplied host. Any failure
/// is returned as a failed rather than thrown.
/// A returned partial plugin/context remains caller-owned; this method never
/// requests unload because the caller must close host registrations first.
///
public static LoadedPlugin Load(string pluginDirectory, PluginManifest manifest, IPluginHost host)
{
ArgumentException.ThrowIfNullOrWhiteSpace(pluginDirectory);
ArgumentNullException.ThrowIfNull(manifest);
ArgumentNullException.ThrowIfNull(host);
// Refuse a contract we cannot honour BEFORE loading any code from it.
// Checking after the fact is not equivalent: the assembly is already in
// a collectible context, and the mismatch surfaces as a type-load or
// missing-member failure from inside the plugin, which reads like the
// plugin is broken rather than built for a different host.
if (!PluginApi.IsSupported(manifest.ApiVersion))
return new LoadedPlugin(
manifest,
Plugin: null,
LoadContext: null,
Error: new PluginApiVersionException(
$"plugin '{manifest.Id}' declares apiVersion {manifest.ApiVersion}, "
+ $"but this build supports {PluginApi.MinimumSupported}"
+ $"..{PluginApi.Current}"));
var dllPath = Path.Combine(pluginDirectory, manifest.EntryDll);
if (!File.Exists(dllPath))
return new LoadedPlugin(
manifest,
Plugin: null,
LoadContext: null,
Error: new FileNotFoundException($"entry dll not found: {dllPath}", dllPath));
PluginAssemblyLoadContext? alc = null;
IAcDreamPlugin? instance = null;
try
{
alc = new PluginAssemblyLoadContext(pluginDirectory, dllPath);
var asm = alc.LoadFromAssemblyPath(dllPath);
IEnumerable types;
try
{
types = asm.GetTypes();
}
catch (ReflectionTypeLoadException rtle)
{
types = rtle.Types.OfType();
}
var pluginType = types
.FirstOrDefault(t => !t.IsAbstract && typeof(IAcDreamPlugin).IsAssignableFrom(t));
if (pluginType is null)
{
return new LoadedPlugin(
manifest,
Plugin: null,
LoadContext: alc,
Error: new InvalidOperationException(
$"no IAcDreamPlugin implementation found in {manifest.EntryDll}"));
}
instance = (IAcDreamPlugin)Activator.CreateInstance(pluginType)!;
instance.Initialize(host);
return new LoadedPlugin(manifest, instance, alc, Error: null);
}
catch (Exception ex)
{
// The caller owns rollback for a partial instance/context. In
// particular, Initialize may already have attached host callbacks;
// the per-plugin host scope must remove those registrations before
// Disable or any collectible unload request can run.
return new LoadedPlugin(
manifest,
Plugin: instance,
LoadContext: alc,
Error: ex);
}
}
}