#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