using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.Loader; namespace LANCommander.SDK.Plugins; /// /// An isolated for a single plugin. Uses an /// to resolve the plugin's private dependencies while /// deferring host-provided assemblies (the SDK, DI abstractions, Avalonia, etc.) to the default /// context so that shared types keep a single identity across the ALC boundary. /// public sealed class PluginLoadContext : AssemblyLoadContext { private readonly AssemblyDependencyResolver _resolver; /// /// Simple assembly names that must always resolve to the host's already-loaded copy so that /// contract types (, DI, event bus, UI contracts) share identity. Matched /// as a prefix so, e.g., all Avalonia.* assemblies are covered. /// private static readonly string[] SharedAssemblyPrefixes = { "LANCommander.SDK", "LANCommander.Launcher.Plugins", "LANCommander.Server.Services", "Microsoft.Extensions.DependencyInjection", "Microsoft.Extensions.Logging", "Microsoft.Extensions.Hosting", "Microsoft.Extensions.Options", "Microsoft.Extensions.Configuration", "Avalonia", "CommunityToolkit.Mvvm", "System.Management.Automation", }; public PluginLoadContext(string mainAssemblyPath) : base(name: $"Plugin:{System.IO.Path.GetFileNameWithoutExtension(mainAssemblyPath)}", isCollectible: false) { _resolver = new AssemblyDependencyResolver(mainAssemblyPath); } protected override Assembly? Load(AssemblyName assemblyName) { if (IsShared(assemblyName.Name)) return null; // defer to the default context for shared/host-provided assemblies var path = _resolver.ResolveAssemblyToPath(assemblyName); return path is null ? null : LoadFromAssemblyPath(path); } protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) { var path = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); return path is null ? IntPtr.Zero : LoadUnmanagedDllFromPath(path); } private static bool IsShared(string? simpleName) { if (string.IsNullOrEmpty(simpleName)) return false; foreach (var prefix in SharedAssemblyPrefixes) { if (simpleName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return true; } return false; } }