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); } } }