#region CPL License /* Nuclex Framework Copyright (C) 2002-2012 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library 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 IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; namespace Nuclex.Support.Plugins { /// Stores loaded plugins /// /// This class manages a set of assemblies that have been dynamically loaded /// as plugins. It usually is shared by multiple PluginHosts that handle /// different interfaces of one plugin type. /// public class PluginRepository { #region class DefaultAssemblyLoader /// Default assembly loader used to read assemblies from files public class DefaultAssemblyLoader : IAssemblyLoader { /// Initializes a new default assembly loader /// /// Made protected to provide users with a small incentive for using /// the Instance property instead of creating new instances all around. /// protected DefaultAssemblyLoader() { } /// Loads an assembly from a file system path /// Path the assembly will be loaded from /// The loaded assembly protected virtual Assembly LoadAssemblyFromFile(string path) { return Assembly.LoadFrom(path); } /// Tries to loads an assembly from a file /// Path to the file that is loaded as an assembly /// /// Output parameter that receives the loaded assembly or null /// /// True if the assembly was loaded successfully, otherwise false public bool TryLoadFile(string path, out Assembly loadedAssembly) { // A lot of errors can occur when attempting to load an assembly... try { loadedAssembly = LoadAssemblyFromFile(path); return true; } #if WINDOWS // File not found - Most likely a missing dependency of the assembly we // attempted to load since the assembly itself has been found by the GetFiles() method catch(DllNotFoundException) { reportError( "Assembly '" + path + "' or one of its dependencies is missing" ); } #endif // Unauthorized acccess - Either the assembly is not trusted because it contains // code that imposes a security risk on the system or a user rights problem catch(UnauthorizedAccessException) { reportError( "Not authorized to load assembly '" + path + "', " + "possible rights problem" ); } // Bad image format - This exception is often thrown when the assembly we // attempted to load requires a different version of the .NET framework catch(BadImageFormatException) { reportError( "'" + path + "' is not a .NET assembly, requires a different version " + "of the .NET Runtime or does not support the current instruction set (x86/x64)" ); } // Unknown error - Our last resort is to show a default error message catch(Exception exception) { reportError( "Failed to load plugin assembly '" + path + "': " + exception.Message ); } loadedAssembly = null; return false; } /// The only instance of the DefaultAssemblyLoader public static readonly DefaultAssemblyLoader Instance = new DefaultAssemblyLoader(); } #endregion // class DefaultAssemblyLoader /// Triggered whenever a new assembly is loaded into this repository public event AssemblyLoadEventHandler AssemblyLoaded; /// Initializes a new instance of the plugin repository public PluginRepository() : this(DefaultAssemblyLoader.Instance) { } /// Initializes a new instance of the plugin repository /// /// Loader to use for loading assemblies into this repository /// public PluginRepository(IAssemblyLoader loader) { this.assemblies = new List(); this.assemblyLoader = loader; } /// Loads all plugins matching a wildcard specification /// Path of one or more plugins via wildcard /// /// This function always assumes that a plugin is optional. This means that /// even when you specify a unique file name and a matching file is not found, /// no exception will be raised and the error is silently ignored. /// public void AddFiles(string wildcard) { string directory = Path.GetDirectoryName(wildcard); string search = Path.GetFileName(wildcard); // If no directory was specified, use the current working directory if((directory == null) || (directory == string.Empty)) { directory = "."; } // We'll scan the specified directory for all files matching the specified // wildcard. If only a single file is specified, only that file will match // the supposed wildcard and everything works as expected string[] assemblyFiles = Directory.GetFiles(directory, search); foreach(string assemblyFile in assemblyFiles) { Assembly loadedAssembly; if(this.assemblyLoader.TryLoadFile(assemblyFile, out loadedAssembly)) { AddAssembly(loadedAssembly); } } } /// Adds the specified assembly to the repository /// /// Also used internally, so any assembly that is to be put into the repository, /// not matter how, wanders through this method /// public void AddAssembly(Assembly assembly) { this.assemblies.Add(assembly); // Trigger event in case any subscribers have been registered if(AssemblyLoaded != null) { AssemblyLoaded(this, new AssemblyLoadEventArgs(assembly)); } } /// List of all loaded plugin assemblies in the repository public List LoadedAssemblies { get { return this.assemblies; } } /// Reports an error to the debugging console /// Error message that will be reported private static void reportError(string error) { #if WINDOWS Trace.WriteLine(error); #endif } /// Loaded plugin assemblies private List assemblies; /// Takes care of loading assemblies for the repositories private IAssemblyLoader assemblyLoader; } } // namespace Nuclex.Support.Plugins