/* * Copyright (c) 2022 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Extensions.Loading * File: AssemblyLoader.cs * * AssemblyLoader.cs is part of VNLib.Plugins.Extensions.Loading which is part of the larger * VNLib collection of libraries and utilities. * * VNLib.Plugins.Extensions.Loading is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * VNLib.Plugins.Extensions.Loading is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ using System; using System.Linq; using System.Threading; using System.Reflection; using System.Runtime.Loader; using System.Collections.Generic; using McMaster.NETCore.Plugins; using VNLib.Utils.Resources; namespace VNLib.Plugins.Extensions.Loading { /// /// /// Represents a disposable assembly loader wrapper for /// exporting a signle type from a loaded assembly /// /// /// If the loaded type implements the /// dispose method is called when the loader is disposed /// /// /// The exported type to manage public class AssemblyLoader : OpenResourceHandle { private readonly PluginLoader _loader; private readonly CancellationTokenRegistration _reg; private readonly Lazy _instance; /// /// The instance of the loaded type /// public override T Resource => _instance.Value; private AssemblyLoader(PluginLoader loader, in CancellationToken unloadToken) { _loader = loader; //Init lazy loader _instance = new(LoadAndGetExportedType, LazyThreadSafetyMode.PublicationOnly); //Register dispose _reg = unloadToken.Register(Dispose); } /// /// Loads the default assembly and gets the expected export type, /// creates a new instance, and calls its parameterless constructor /// /// The desired type instance /// private T LoadAndGetExportedType() { //Load the assembly Assembly asm = _loader.LoadDefaultAssembly(); Type resourceType = typeof(T); //See if the type is exported Type exp = (from type in asm.GetExportedTypes() where resourceType.IsAssignableFrom(type) select type) .FirstOrDefault() ?? throw new EntryPointNotFoundException($"Imported assembly does not export desired type {resourceType.FullName}"); //Create instance return (T)Activator.CreateInstance(exp)!; } /// /// Creates a method delegate for the given method name from /// the instance wrapped by the current loader /// /// /// The name of the method to recover /// The delegate method wrapper if found, null otherwise /// /// public TDelegate? TryGetMethod(string methodName) where TDelegate : Delegate { //get the type info of the actual resource return Resource!.GetType() .GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance) ?.CreateDelegate(Resource); } /// protected override void Free() { //If the instance is disposable, call its dispose method on unload if (_instance.IsValueCreated && _instance.Value is IDisposable) { (_instance.Value as IDisposable)?.Dispose(); } _loader.Dispose(); _reg.Dispose(); } /// /// Creates a new assembly loader for the specified type and /// /// The name of the assmbly within the current plugin directory /// The plugin unload token internal static AssemblyLoader Load(string assemblyName, CancellationToken unloadToken) { Assembly executingAsm = Assembly.GetExecutingAssembly(); AssemblyLoadContext currentCtx = AssemblyLoadContext.GetLoadContext(executingAsm) ?? throw new InvalidOperationException("Could not get default assembly load context"); List shared = new () { typeof(T), typeof(PluginBase), }; //Share all VNLib internal libraries shared.AddRange(currentCtx.Assemblies.Where(static s => s.FullName.Contains("VNLib", StringComparison.OrdinalIgnoreCase)).SelectMany(static s => s.GetExportedTypes())); PluginLoader loader = PluginLoader.CreateFromAssemblyFile(assemblyName, currentCtx.IsCollectible, shared.ToArray(), conf => { /* * Load context is required to be set to the executing assembly's load context * because it is controlled by the host, so this loader should be considered a * a "child" collection of assemblies */ conf.DefaultContext = currentCtx; conf.PreferSharedTypes = true; //Share utils asm conf.SharedAssemblies.Add(typeof(Utils.Memory.Memory).Assembly.GetName()); }); return new(loader, in unloadToken); } } }