/// <summary> /// Loads plugins with the specified plugin interface type. /// </summary> /// <param name="path"> /// The path to look for plugins to load. /// </param> /// <param name="domains">The <see cref="AppDomain"/>s created by the loader.</param> /// <param name="saveToZip"> /// Tells this function to see if the plugin was saved to a zip file and it's pdb file as well. /// </param> /// <returns> /// A list of plugins loaded that derive from the specified type. /// </returns> public ICollection <T> LoadPlugins(string path, out List <AppDomain> domains, bool saveToZip = false) #endif { string[] dllFileNames = null; if (Directory.Exists(path)) { dllFileNames = Directory.GetFiles(path, "*.dll"); } // try to load from a zip as well if plugins are installed in both places. var zippath = $"{path}.zip"; ICollection <T> plugins = new List <T>(); #if NET5_0_OR_GREATER contexts = new List <PluginLoadContext>(); #elif NET45 domains = new List <AppDomain>(); #endif // handle when path points to a zip file. if (Directory.Exists(path) || File.Exists(zippath)) { ICollection <Assembly> assemblies = new List <Assembly>(); if (dllFileNames != null) { foreach (var dllFile in dllFileNames) { #if NET5_0_OR_GREATER var context = new PluginLoadContext($"Plugin#{dllFileNames.ToList().IndexOf(dllFile)}", path); #else var domain = AppDomain.CreateDomain($"Plugin#{dllFileNames.ToList().IndexOf(dllFile)}"); #endif try { var asmFile = File.ReadAllBytes(dllFile); // We need to handle the case where the pdb does not exist and where the // symbols might actually be embedded inside the dll instead or simply does // not exist yet. #if NET5_0_OR_GREATER var pdbFile = Debugger.IsAttached && File.Exists(dllFile.Replace("dll", "pdb", StringComparison.Ordinal)) ? File.ReadAllBytes( dllFile.Replace("dll", "pdb", StringComparison.Ordinal)) : null; using var ms1 = new MemoryStream(asmFile); using var ms2 = new MemoryStream(pdbFile); var assembly = Debugger.IsAttached && pdbFile != null? context.LoadFromStream(ms1, ms2) : context.LoadFromStream(ms1); contexts.Add(context); #else var pdbFile = Debugger.IsAttached && File.Exists(dllFile.Replace("dll", "pdb")) ? File.ReadAllBytes(dllFile.Replace("dll", "pdb")) : null; var assembly = Debugger.IsAttached && pdbFile != null? domain.Load(asmFile, pdbFile) : domain.Load(asmFile); domains.Add(domain); #endif assemblies.Add(assembly); } catch (BadImageFormatException) { // ignore the error and load the other files. #if NET5_0_OR_GREATER context.Unload(); #else AppDomain.Unload(domain); #endif } catch (FileLoadException) { #if NET5_0_OR_GREATER var assembly = context.LoadFromAssemblyPath(dllFile); contexts.Add(context); assemblies.Add(assembly); #elif NET45 try { var assembly = domain.Load(AssemblyName.GetAssemblyName(dllFile)); domains.Add(domain); assemblies.Add(assembly); } catch (Exception) { // ignore the error and load the other files. AppDomain.Unload(domain); } #elif !NET45 var assembly = Assembly.LoadFrom(dllFile); domains.Add(domain); assemblies.Add(assembly); #endif } catch (FileNotFoundException) { // ignore the error and load other files. #if NET5_0_OR_GREATER context.Unload(); #else AppDomain.Unload(domain); #endif } } } if (saveToZip && File.Exists(zippath)) { var filesInZip = new Dictionary <string, int>(); using (var zipFile = ZipFile.OpenRead(zippath)) { foreach (var entry in zipFile.Entries) { filesInZip.Add(entry.FullName, zipFile.Entries.IndexOf(entry)); } } foreach (var entry in filesInZip.Keys) { // just lookup the dlls here. The LoadFromZip method will load the pdb’s if they are deemed needed. if (entry.EndsWith(".dll", StringComparison.Ordinal)) { #if NET5_0_OR_GREATER var context = new PluginLoadContext($"ZipPlugin#{filesInZip[entry]}", path); var assembly = ZipAssembly.LoadFromZip(zippath, entry, context); contexts.Add(context); #else var domain = AppDomain.CreateDomain($"ZipPlugin#{filesInZip[entry]}"); var assembly = ZipAssembly.LoadFromZip(zippath, entry, domain); domains.Add(domain); #endif if (assembly != null) { assemblies.Add(assembly); } } } } var pluginType = typeof(T); var pluginTypes = new List <Type>(); foreach (var assembly in assemblies) { if (assembly != null) { try { var types = assembly.GetTypes(); foreach (var type in types) { if (!type.IsInterface && !type.IsAbstract && type.GetInterface(pluginType.FullName) != null) { pluginTypes.Add(type); } } } catch (ReflectionTypeLoadException ex) { var exMsg = new StringBuilder(); foreach (var exceptions in ex.LoaderExceptions) { exMsg.Append($"{exceptions.Message}{Environment.NewLine}{exceptions.StackTrace}{Environment.NewLine}"); } PluginLoaderMessage?.Invoke(null, new MessageEventArgs(exMsg.ToString(), "Error!", ErrorLevel.Error)); } catch (ArgumentNullException ex) { var exMsg = string.Empty; LogException(ex, ref exMsg); PluginLoaderMessage?.Invoke(null, new MessageEventArgs(exMsg, "Error!", ErrorLevel.Error)); } } } ICollection <int> toRemove = new List <int>(); foreach (var type in pluginTypes) { try { var plugin = (T)Activator.CreateInstance(type); plugins.Add(plugin); } catch (MissingMethodException) { toRemove.Add(pluginTypes.IndexOf(type)); var index = 0; #if NET5_0_OR_GREATER foreach (var context in contexts) { if (context.Assemblies.Contains(type.Assembly) && context.IsCollectible) { index = contexts.IndexOf(context); context.Unload(); } } #else foreach (var domain in domains) { if (domain.GetAssemblies().ToList().Contains(type.Assembly)) { index = domains.IndexOf(domain); AppDomain.Unload(domain); } } #endif if (index > -1) { #if NET5_0_OR_GREATER contexts.RemoveAt(index); #else domains.RemoveAt(index); #endif } } } foreach (var indexToRemove in toRemove) { if (indexToRemove > -1) { pluginTypes.RemoveAt(indexToRemove); } } toRemove.Clear(); return(plugins); } return(plugins); }
/// <summary> /// Loads plugins with the specified plugin interface type. /// </summary> /// <param name="path">The path to look for plugins to load.</param> /// <param name="saveToZip">Tells this function to see if the plugin was saved to a zip file and it's pdb file as well.</param> /// <param name="loadPDBFile">Tells the method to load the plugins pdb file or not. Ignored and loaded anyway when a debugger is attached.</param> /// <returns>A list of plugins loaded that derive from the specified type.</returns> public ICollection <T> LoadPlugins(string path, bool saveToZip = false, bool loadPDBFile = false) { string[] dllFileNames = null; if (Directory.Exists(path)) { dllFileNames = Directory.GetFiles(path, "*.dll"); } // try to load from a zip as well if plugins are installed in both places. var zippath = $"{path}.zip"; ICollection <T> plugins = new List <T>(); // handle when path points to a zip file. if (Directory.Exists(path) || File.Exists(zippath)) { ICollection <Assembly> assemblies = new List <Assembly>(); if (dllFileNames != null) { foreach (var dllFile in dllFileNames) { var loadPDB = loadPDBFile ? loadPDBFile : Debugger.IsAttached; try { var asmFile = File.ReadAllBytes(dllFile); try { #if NETSTANDARD2_1 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 || NETCOREAPP3_1 || NET5_0 var pdbFile = loadPDB ? File.ReadAllBytes(dllFile.Replace("dll", "pdb", StringComparison.Ordinal)) : null; #else var pdbFile = loadPDB ? File.ReadAllBytes(dllFile.Replace("dll", "pdb")) : null; #endif var assembly = loadPDB ? Assembly.Load(asmFile, pdbFile) : Assembly.Load(asmFile); assemblies.Add(assembly); } catch (FileNotFoundException) { var assembly = Assembly.Load(asmFile); assemblies.Add(assembly); } } catch (BadImageFormatException) { // ignore the error and load the other files. } catch (FileLoadException) { var assembly = Assembly.LoadFrom(dllFile); assemblies.Add(assembly); } catch (FileNotFoundException) { // ignore the error and load other files. } } } if (saveToZip && File.Exists(zippath)) { using (var zipFile = ZipFile.OpenRead(zippath)) { foreach (var entry in zipFile.Entries) { // just lookup the dlls here. The LoadFromZip method will load the pdb’s if they are deemed needed. if (entry.FullName.EndsWith(".dll", StringComparison.Ordinal)) { var assembly = ZipAssembly.LoadFromZip(zippath, entry.FullName, loadPDBFile); if (assembly != null) { assemblies.Add(assembly); } } } } } var pluginType = typeof(T); ICollection <Type> pluginTypes = new List <Type>(); foreach (var assembly in assemblies) { if (assembly != null) { try { var types = assembly.GetTypes(); foreach (var type in types) { if (!type.IsInterface && !type.IsAbstract && type.GetInterface(pluginType.FullName) != null) { pluginTypes.Add(type); } } } catch (ReflectionTypeLoadException ex) { var exMsg = new StringBuilder(); foreach (var exceptions in ex.LoaderExceptions) { exMsg.Append($"{exceptions.Message}{Environment.NewLine}{exceptions.StackTrace}{Environment.NewLine}"); } PluginLoaderMessage?.Invoke(null, new MessageEventArgs(exMsg.ToString(), "Error!", ErrorLevel.Error)); } catch (ArgumentNullException ex) { var exMsg = string.Empty; LogException(ex, ref exMsg); PluginLoaderMessage?.Invoke(null, new MessageEventArgs(exMsg, "Error!", ErrorLevel.Error)); } } } foreach (var type in pluginTypes) { var plugin = (T)Activator.CreateInstance(type); plugins.Add(plugin); } return(plugins); } return(plugins); }