I was developing a small plugin framework and noticed that I couldn't successfully use Type.IsAssignableFrom()
to check if a plugin class implements the type IPlugin
. I had checked other SO questions (such as this) to see why the function returned false, but to my surprise, none of the suggestions worked.
I had a class inside a plugin assembly which implements the PluginAPI.IPlugin
class in a referenced assembly. When checking the AssemblyQualifiedName
for both my PluginAPI.IPlugin
type as well as the types for the plugin class's list of interfaces, I found no difference whatsoever. Both outputed the value:
PluginAPI.IPlugin, PluginAPI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Upon further investigation, I checked Type.cs source and found that the function IsAssignableFrom()
was failing in an internal call where it checks for type equality (via ==
).
I had found that modifying my plugin framework to load plugin assemblies in a typical execution context (Assembly.LoadFrom
) as opposed to a reflection only context (Assembly.ReflectionOnlyLoadFrom
) allowed the type equality check to evaluate to true, which is what I had expected all along.
Why is it that the reflection-only context causes the types to no longer be equal?
Code:
The following section contains relevant code for reproducing the problem as well as description on the projects were set up.
Assembly 1: PluginAPI
This assembly only contains IPlugin.cs:
namespace PluginAPI
{
public interface IPlugin
{
}
}
Assembly 2: Plugin1
This assembly contains several plugins (classes that implement PluginAPI.IPlugin
). There is Calculator.cs and WPFPlugin1.cs
namespace Plugin1
{
public class Calculator : IPlugin
{
public Calculator()
{
}
}
}
namespace Plugin1
{
public class WPFPlugin1 : IPlugin
{
public WPFPlugin1()
{
}
}
}
Assembly 3: AppDomainTest
This assembly contains the entry point and tests loading the plugin assemblies in a separate AppDomain so they can be unloaded.
namespace AppDomainTest
{
public class AppDomainTest
{
public static void Main(String[] args)
{
AppDomain pluginInspectionDomain = AppDomain.CreateDomain("PluginInspectionDomain");
PluginInspector inspector = new PluginInspector();
pluginInspectionDomain.DoCallBack(inspector.Callback);
AppDomain.Unload(pluginInspectionDomain);
Console.ReadKey();
}
}
}
namespace AppDomainTest
{
[Serializable]
public class PluginInspector
{
public void Callback()
{
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += CurrentDomain_ReflectionOnlyAssemblyResolve;
//TODO: Change this to the output directory of the Plugin1.dll.
string PluginDirectory = @"H:\Projects\SOTest\Plugin1\bin\Debug";
DirectoryInfo dir = new DirectoryInfo(PluginDirectory);
if (dir.Exists)
{
FileInfo[] dlls = dir.GetFiles("*.dll");
//Check if the dll has a "Plugin.config" and if it has any plugins.
foreach (FileInfo dll in dlls)
{
Console.WriteLine(String.Format("DLL Found: {0}", dll.FullName));
LoadAssembly(dll.FullName);
}
}
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= CurrentDomain_ReflectionOnlyAssemblyResolve;
}
private void LoadAssembly(string path)
{
Console.WriteLine(String.Format("\tLoading Assembly"));
//From within the PluginInspectionDomain, load the assembly in a reflection only context.
Assembly[] loadedAssemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();
//Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
AssemblyName assemblyName = AssemblyName.GetAssemblyName(path);
bool assemblyAlreadyLoaded = loadedAssemblies.Any(new Func<Assembly, bool>((Assembly a) =>
{
//If the assembly full names match, then they are identical.
return (assemblyName.FullName.Equals(a.FullName));
}));
if (assemblyAlreadyLoaded)
{
Console.WriteLine("Assembly already loaded: {0}, exiting function early.", assemblyName.FullName);
return;
}
//Assembly not already loaded, check to see if it has any plugins.
Assembly assembly = Assembly.ReflectionOnlyLoadFrom(path);
//Assembly assembly = Assembly.LoadFrom(path);
GetPlugins(assembly);
}
private Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
{
Console.WriteLine("Resolving assembly: {0}", args.Name);
//This callback is called each time the current AppDomain attempts to resolve an assembly.
//Make sure we check for any plugins in the referenced assembly.
Assembly assembly = Assembly.ReflectionOnlyLoad(args.Name);
//Assembly assembly = Assembly.Load(args.Name);
if (assembly == null)
{
throw new TypeLoadException("Could not load assembly: " + args.Name);
}
GetPlugins(assembly);
return assembly;
}
/// <summary>
/// This function takes an assembly and extracts the Plugin.config file and parses it
/// to determine which plugins are included in the assembly and to check if they point to a valid main class.
/// </summary>
/// <param name="assembly"></param>
public List<IPlugin> GetPlugins(Assembly assembly)
{
Console.WriteLine("Searching for plugins inside assembly: " + assembly.FullName);
using (Stream resource = assembly.GetManifestResourceStream(assembly.GetName().Name + ".Plugin.config"))
{
if (resource != null)
{
//For brevity I have ommitted the types PluginConfiguration and Plugin from the PluginAPI assembly
//since they are only container classes generated from XML that have string properties.
//These are used to find which classes should be checked for implementing IPlugin.
//Parse the Plugin.config file.
XmlSerializer serializer = new XmlSerializer(typeof(PluginConfiguration));
PluginConfiguration configuration = (PluginConfiguration)serializer.Deserialize(resource);
if (configuration == null)
{
Console.WriteLine("Configuration is null.");
}
if (configuration.Plugins == null)
{
Console.WriteLine("Could not find any plugins. It's null.");
}
Console.WriteLine();
foreach (Type type in assembly.GetExportedTypes())
{
Console.WriteLine("Type: FullName=\"{0}\"", type.FullName);
}
Console.WriteLine("\nAuthor: {0}\tVersion: {1}", configuration.Author, configuration.Version);
Console.WriteLine("========================================");
foreach (Plugin pluginDescriptor in configuration.Plugins)
{
Console.WriteLine(String.Format("Plugin: Name={0}, MainClass={1}", pluginDescriptor.Name, pluginDescriptor.MainClass));
bool containsType = false;
foreach (Type type in assembly.GetExportedTypes())
{
if (type.FullName.Equals(pluginDescriptor.MainClass))
{
containsType = true;
if (typeof(IPlugin).IsAssignableFrom(type))
{
Console.WriteLine("MainClass \'{0}\' implements PluginAPI.IPlugin", pluginDescriptor.MainClass);
}
Console.WriteLine("Checking for {0}", typeof(IPlugin).AssemblyQualifiedName);
Console.WriteLine("Interfaces:");
foreach (Type interfaceType in type.GetInterfaces())
{
Console.WriteLine("> {0}", interfaceType.AssemblyQualifiedName);
if (interfaceType == typeof(IPlugin))
{
//This is NOT executed if in reflection-only context.
Console.WriteLine("interface is equal to IPlugin");
}
}
}
}
Console.WriteLine((containsType ? "Found \'" + pluginDescriptor.MainClass + "\' inside assembly. Plugin is available." : "The MainClass type could not be resolved. Plugin unavailable."));
}
}
}
Console.WriteLine("Done searching for plugins inside assembly: " + assembly.FullName + "\n");
return null;
}
}
}
Aucun commentaire:
Enregistrer un commentaire