aboutsummaryrefslogtreecommitdiff
path: root/lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-01-09 15:09:13 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-01-09 15:09:13 -0500
commita46c3bf452d287b50b2e7dd5a24f5995c9fd26f6 (patch)
tree3a978b2dd2887b5c0e25f595516594a647d8e880 /lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs
parent189c6714057bf45553847eaeb9ce97eb7272eb8c (diff)
Restructure
Diffstat (limited to 'lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs')
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs162
1 files changed, 162 insertions, 0 deletions
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs b/lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs
new file mode 100644
index 0000000..5baf123
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs
@@ -0,0 +1,162 @@
+/*
+* 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 General Public License as published
+* by the Free Software Foundation, either version 2 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
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Plugins.Extensions.Loading. If not, see http://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
+{
+ /// <summary>
+ /// <para>
+ /// Represents a disposable assembly loader wrapper for
+ /// exporting a signle type from a loaded assembly
+ /// </para>
+ /// <para>
+ /// If the loaded type implements <see cref="IDisposable"/> the
+ /// dispose method is called when the loader is disposed
+ /// </para>
+ /// </summary>
+ /// <typeparam name="T">The exported type to manage</typeparam>
+ public class AssemblyLoader<T> : OpenResourceHandle<T>
+ {
+ private readonly PluginLoader _loader;
+ private readonly CancellationTokenRegistration _reg;
+ private readonly Lazy<T> _instance;
+
+ /// <summary>
+ /// The instance of the loaded type
+ /// </summary>
+ 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);
+ }
+
+ /// <summary>
+ /// Loads the default assembly and gets the expected export type,
+ /// creates a new instance, and calls its parameterless constructor
+ /// </summary>
+ /// <returns>The desired type instance</returns>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ 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)!;
+ }
+
+ /// <summary>
+ /// Creates a method delegate for the given method name from
+ /// the instance wrapped by the current loader
+ /// </summary>
+ /// <typeparam name="TDelegate"></typeparam>
+ /// <param name="methodName">The name of the method to recover</param>
+ /// <returns>The delegate method wrapper if found, null otherwise</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="AmbiguousMatchException"></exception>
+ public TDelegate? TryGetMethod<TDelegate>(string methodName) where TDelegate : Delegate
+ {
+ //get the type info of the actual resource
+ return Resource!.GetType()
+ .GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance)
+ ?.CreateDelegate<TDelegate>(Resource);
+ }
+
+ ///<inheritdoc/>
+ 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();
+ }
+
+ /// <summary>
+ /// Creates a new assembly loader for the specified type and
+ /// </summary>
+ /// <param name="assemblyName">The name of the assmbly within the current plugin directory</param>
+ /// <param name="unloadToken">The plugin unload token</param>
+ internal static AssemblyLoader<T> 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<Type> 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);
+ }
+ }
+}