我正在开发一个小插件框架,并注意到我无法成功使用Type.IsAssignableFrom()
来检查插件类是否实现了类型IPlugin
。我检查了其他SO问题(such as this),看看为什么函数返回false,但令我惊讶的是,没有一个建议有效。
我在插件程序集中有一个类,它在引用的程序集中实现PluginAPI.IPlugin
类。当检查AssemblyQualifiedName
我的PluginAPI.IPlugin
类型以及插件类的接口列表的类型时,我发现没有任何区别。两者都输出了值:
PluginAPI.IPlugin,PluginAPI,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = null
经过进一步调查,我检查了Type.cs source,发现函数IsAssignableFrom()
在内部调用中失败,它检查类型是否相等(通过==
)。
我发现修改我的插件框架以在典型的执行上下文(Assembly.LoadFrom
)中加载插件程序集而不是仅反射上下文(Assembly.ReflectionOnlyLoadFrom
)允许类型相等性检查评估为< em> true ,这是我一直期待的。
<小时/>
以下部分包含重现问题的相关代码以及已设置项目的说明。
程序集1:PluginAPI
此程序集仅包含IPlugin.cs:
namespace PluginAPI
{
public interface IPlugin
{
}
}
程序集2:Plugin1
此程序集包含多个插件(实现PluginAPI.IPlugin
的类)。
namespace Plugin1
{
public class MyPlugin : IPlugin
{
public MyPlugin()
{
}
}
}
程序集3:AppDomainTest
此程序集包含入口点,并测试将插件程序集加载到单独的AppDomain中,以便可以卸载它们。
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();
}
}
}
/// <summary>
/// If using this class. You can comment/uncomment at the lines provided
/// where Assembly.Load* vs. Assembly.ReflectionOnlyLoad* are used.
/// You should notice Assembly.Load* succeeds in determining type equality;
/// however, Assembly.ReflectionOnlyLoad* fails.
/// </summary>
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)
{
LoadAssembly(dll.FullName);
}
}
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= CurrentDomain_ReflectionOnlyAssemblyResolve;
}
private void LoadAssembly(string path)
{
//From within the PluginInspectionDomain, load the assembly in a reflection only context.
Assembly[] loadedAssemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();
//TODO (toggle comment): 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)
{
//Assembly already loaded. No need to search twice for plugins.
return;
}
//Assembly not already loaded, check to see if it has any plugins.
Assembly assembly = Assembly.ReflectionOnlyLoadFrom(path);
//TODO (toggle comment): Assembly assembly = Assembly.LoadFrom(path);
GetPlugins(assembly);
}
private Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
{
//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);
//TODO (toggle comment): 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)
{
using (Stream resource = assembly.GetManifestResourceStream(assembly.GetName().Name + ".Plugin.config"))
{
if (resource != null)
{
//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("Configuration contains no plugins.");
}
foreach (Plugin pluginDescriptor in configuration.Plugins)
{
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."));
}
}
}
return null;
}
}
}
如果对我如何使用XML指定插件配置感到好奇,请查看这些文件。请注意,重现问题不需要XML解析。例如,你可以硬编码一些类型的字符串来搜索和比较,但这就是我选择这样做的方式。这也为那些有兴趣复制我的插件加载器的人提供了参考。
XML架构:PluginConfiguration.xsd
<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="PluginConfiguration"
targetNamespace="clr-namespace:PluginAPI;assembly=PluginAPI/PluginConfiguration.xsd"
elementFormDefault="qualified"
xmlns="clr-namespace:PluginAPI;assembly=PluginAPI/PluginConfiguration.xsd"
xmlns:mstns="clr-namespace:PluginAPI;assembly=PluginAPI/PluginConfiguration.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="PluginConfiguration">
<xs:complexType>
<xs:sequence>
<xs:element name="Plugin" type="PluginType" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="Author" type="xs:string"/>
<xs:attribute name="Version" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:complexType name="PluginType">
<xs:attribute name="Name" type="xs:string" use="required"/>
<xs:attribute name="MainClass" type="ClassType" use="required"/>
</xs:complexType>
<xs:simpleType name="ClassType">
<xs:restriction base="xs:string">
<xs:annotation>
<xs:documentation>
Matches class names that follow the format: IDENTIFIER(.IDENTIFIER)*
This pattern is necessary for validating fully qualified class paths.
</xs:documentation>
</xs:annotation>
<xs:pattern value="^([_a-zA-Z]+([_a-zA-Z0-9])*)(\.?[_a-zA-Z]+([_a-zA-Z0-9])*)*$"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
PluginConfiguration.cs
namespace PluginAPI
{
/// <remarks/>
[XmlType(AnonymousType = true, Namespace = "clr-namespace:PluginAPI;assembly=PluginAPI/PluginConfiguration.xsd")]
[XmlRoot(ElementName = "PluginConfiguration", Namespace = "clr-namespace:PluginAPI;assembly=PluginAPI/PluginConfiguration.xsd", IsNullable = false)]
public partial class PluginConfiguration
{
private Plugin[] pluginField;
private string authorField;
private string versionField;
/// <remarks/>
[XmlElement("Plugin", typeof(Plugin))]
public Plugin[] Plugins
{
get { return this.pluginField; }
set { this.pluginField = value; }
}
/// <remarks/>
[XmlAttribute()]
public string Author
{
get { return this.authorField; }
set { this.authorField = value; }
}
/// <remarks/>
[XmlAttribute()]
public string Version
{
get { return this.versionField; }
set { this.versionField = value; }
}
}
/// <remarks/>
[XmlType(AnonymousType = true, Namespace = "clr-namespace:PluginAPI;assembly=PluginAPI/PluginConfiguration.xsd")]
public partial class Plugin
{
private string mainClassField;
private string nameField;
/// <remarks/>
[XmlAttribute()]
public string MainClass
{
get { return this.mainClassField; }
set { this.mainClassField = value; }
}
/// <remarks/>
[XmlAttribute()]
public string Name
{
get { return this.nameField; }
set { this.nameField = value; }
}
}
}
Plugin.config (这是插件程序集中的嵌入式资源)。
<?xml version="1.0" encoding="utf-8" ?>
<PluginConfiguration xmlns="clr-namespace:PluginAPI;assembly=PluginAPI/PluginConfiguration.xsd"
Author="Nicholas Miller"
Version="1.0.0">
<Plugin MainClass="Plugin1.MyPlugin" Name="MyPlugin"/>
</PluginConfiguration>