/* * Copyright (c) 2023 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.IO; using System.Threading; using System.Reflection; using System.Runtime.Loader; using VNLib.Utils.IO; using VNLib.Utils.Resources; namespace VNLib.Plugins.Extensions.Loading { /// /// /// Represents a disposable assembly loader wrapper for /// exporting a single 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 sealed class AssemblyLoader : ManagedLibrary, IDisposable { private readonly CancellationTokenRegistration _reg; private readonly Lazy _instance; private bool disposedValue; /// /// The instance of the loaded type /// public T Resource => _instance.Value; private AssemblyLoader(string assemblyPath, AssemblyLoadContext parentContext, CancellationToken unloadToken) :base(assemblyPath, parentContext) { //Init lazy type loader _instance = new(LoadTypeFromAssembly, LazyThreadSafetyMode.PublicationOnly); //Register dispose _reg = unloadToken.Register(Dispose); } /// /// 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); } private void Dispose(bool disposing) { if (!disposedValue) { //Call base unload during dispose (or finalize) OnUnload(); //Always cleanup registration _reg.Dispose(); if (disposing) { //If the instance is disposable, call its dispose method on unload if (_instance.IsValueCreated && _instance.Value is IDisposable disposable) { disposable.Dispose(); } } disposedValue = true; } } /// /// Cleans up any unused internals /// ~AssemblyLoader() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: false); } /// /// Disposes the assembly loader and cleans up resources. If the /// inherits the intrance is disposed. /// public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } /// /// Creates a new loader for the desired assembly. The assembly and its dependencies /// will be loaded into the specified context. If no context is specified the current assemblie's load /// context is captured. /// /// The name of the assmbly within the current plugin directory /// The plugin unload token /// The assembly load context to load the assmbly into /// internal static AssemblyLoader Load(string assemblyName, AssemblyLoadContext loadContext, CancellationToken unloadToken) { _ = loadContext ?? throw new ArgumentNullException(nameof(loadContext)); //Make sure the file exists if (!FileOperations.FileExists(assemblyName)) { throw new FileNotFoundException($"The desired assembly {assemblyName} could not be found at the file path"); } //Create the loader from its absolute file path FileInfo fi = new(assemblyName); return new(fi.FullName, loadContext, unloadToken); } } }