Reputation: 1047
Ive built a plugin architecture in C#.NET that dynamically loads DLLs from a predefined physical file path. I am aware of assemblies possibly existing in memory location in two locations thus making it unreliable to verify a member (interface) in the assembly using something like...
if(plugin is T)
// cache the assembly
... so currently I am using the interface name to compare, then activating an instance from that. But, this has limitations too because the interface 'IPlugin' is a very common interface name which lots of third party assemblies use, (i.e. log4net, etc)
Take the following code (which is does not work):
foreach (Type t in assembly.GetTypes())
{
type = t;
if (!type.IsInterface)
{
var plugin = type.GetInterface(typeof(T).Name);
if (plugin != null)
if (plugin is T)
{
T p = (T)Activator.CreateInstance(type);
if (!this.Plugins.Select(sp => sp.Name).Contains(p.Name))
this.Plugins.Add(p);
}
}
}
My question: What is the best (reliable) way to verify the dynamically loaded DLL matches the IPlugin interface?
One thought is to hard code the IPlugin's public key token and verify that, but I am wondering if there is a more formal way. As an example, I can imagine a potential security hole with assemblies spoofing the IPlugin name, or public key token... so perhaps there is a good way to test that the DLL loaded matches the signature of the assembly loading it.
Please let me know if more clarity is required.
Very grateful!
Upvotes: 2
Views: 4629
Reputation: 13205
I solve it like so:
public List<T> LoadPluginsFromPath<T>( string Path ) {
List<T> results = new List<T>();
DirectoryInfo Directory = new DirectoryInfo( Path );
if ( !Directory.Exists ) {
return results; // Nothing to do here
}
FileInfo[] files = Directory.GetFiles( "*.dll" );
if ( files != null && files.Length > 0 ) {
foreach ( FileInfo fi in files ) {
List<T> step = LoadPluginFromAssembly( fi.FullName );
if ( step != null && step.Count > 0 ) {
results.AddRange( step );
}
}
}
return results;
}
private List<T> LoadPluginFromAssembly<T>( string Filename ) {
List<T> results = new List<T>();
Type pluginType = typeof( T );
Assembly assembly = Assembly.LoadFrom( Filename );
if ( assembly == null ) {
return results;
}
Type[] types = assembly.GetExportedTypes();
foreach ( Type t in types ) {
if ( !t.IsClass || t.IsNotPublic ) {
continue;
}
if ( pluginType.IsAssignableFrom( t ) ) {
T plugin = Activator.CreateInstance( t ) as T;
if ( plugin != null ) {
results.Add( plugin );
}
}
}
return results;
}
I call it like so:
List<MyPlugin> plugins = LoadPluginsFromPath<MyPlugin>( "plugins" );
Upvotes: 4
Reputation: 11991
Insteas of enumerating all types in assembly, why don't you define a factory class?
The factory class would have a more appropriate name, such as "YourFramework.PluginTypeFactory", indeed eliminating probable name collisions.
Furthermore, Assembly.GetTypes
could fail badly on certain assemblies, and takes a lot of time on bug assemblies.
Upvotes: 2
Reputation: 11
I happened to have the same problem as you do, some "Plugins" were loaded twice and the .NET Framework was not able to resolve the types when using
IsAssignableFrom
I solved it adding a handler to the AppDomain's AssemblyResolve event, and not loading the "Plugin" again if it was already loaded in the Assemblies collection of the current AppDomain.
It happened most when some Plugins started depending on each other and the Assembly loader was going crazy loading the same assemblies over and over again when they were already loaded.
Hope it helped solve your problem, it sure drove me crazy!
Upvotes: 1
Reputation: 141678
Use IsAssignableFrom
:
var yourIPlugin = typeof(IPlugin);
foreach (Type t in assembly.GetTypes())
{
if (yourIPlugin.IsAssignableFrom(t))
{
T p = (T)Activator.CreateInstance(t);
if (!this.Plugins.Select(sp => sp.Name).Contains(p.Name))
this.Plugins.Add(p);
}
}
IsAssignableFrom
uses a type to see if another type can be assigned from it. It takes fully into consideration the actual type, not just the name of the type. So even if your assembly, or other assemblies, contain types named IPlugin
, only the one from yourIPlugin
would be found.
Upvotes: 2