15

We implement a plugin framework for our application and load plugin assemblies using Assembly.Loadfrom. We then use GetTypes() and further examine the types with each plugin file for supported Interfaces.

A path for the plugins is provided by the user and we cycle through each of the files in the folder to see if it (the plugin) supports our plugin interface. If it does, we create an instance, if not we move onto the next file.

We build two versions of software from the one code base (appA_1 and appA_2).

Loading the plugins works well when the plugins are loaded by the application that was built at the same time as the plugin file. However if we build appA_2 and point to the plugin folder of appA_1, we get an exception when GetTypes() is called.

A basic version of our code is;

var pluginAssembly = Assembly.LoadFrom(FileName);    
foreach (var pluginType in pluginAssembly.GetTypes())
{

We get a "ReflectionTypeLoadException" exception.

This is concerning because we want our application to be able to load the types of any plugin, built by anyone. Is there something we are missing?

EDIT: After iterating through the LoaderExceptions we have discovered that there is a single file libPublic.dll that generates a System.IO.FileNotFoundException exception. The strange thing is that this file resides in the application directory and the plugin is referenced to the project file.

EDIT 2: In the exception log we find the following "Comparing the assembly name resulted in the mismatch: Revision Number"

H.B.
  • 166,899
  • 29
  • 327
  • 400
Veskechky
  • 173
  • 1
  • 2
  • 6

3 Answers3

17

A few things:

  • Make sure you don't have duplicate assemblies in the plugin directory (i.e. assemblies that you're already loading in your main app from your app directory.) Otherwise, when you load your plugin, it may load an additional copy of the same assembly. This can lead to fun exceptions like:

    Object (of type 'MyObject') is not of type 'MyObject'.

  • If you're getting the exception when instantiating a type, you may need to handle AppDomain.AssemblyResolve:

    private void App_Startup(object sender, StartupEventArgs e)
    {
        // Since we'll be dynamically loading assemblies at runtime, 
        // we need to add an appropriate resolution path
        // Otherwise weird things like failing to instantiate TypeConverters will happen
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
    }
    
    private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        var domain = (AppDomain) sender;
    
        foreach (var assembly in domain.GetAssemblies())
        {
            if (assembly.FullName == args.Name)
            {
                return assembly;
            }
        }
    
        return null;
    }
    

I realize it's a bit strange to have to tell the CLR that, in order to resolve an assembly, find the assembly with the name we're using to resolve, but I've seen odd things happen without it. For example, I could instantiate types from a plugin assembly, but if I tried to use TypeDescriptor.GetConverter, it wouldn't find the TypeConverter for the class, even though it could see the Converter attribute on the class.


Looking at your edits, this is probably not what's causing your current exception, though you may run into these issues later as you work with your plugins.

Gustavo Mori
  • 8,319
  • 3
  • 38
  • 52
Dan Bryant
  • 27,329
  • 4
  • 56
  • 102
  • 2
    You're awesome! I had the duplicate assembly problem, so this answer saved me! – JoeCool Nov 30 '12 at 16:19
  • 1
    This solved my issue as well. I was loading my plug-in which exposed a custom UITypeEditor and TypeConverters and while my types would load, the editors/converters would not instantiate. Additionally, my plug-ins were located in a sub directory relative to the application, and oddly enough, if I put the plug-in in the same directory as the host application it worked just fine. This solution helped make the plug-ins work regardless of their location. – Mike May 09 '13 at 05:33
  • 1
    You are absolutely correct. I too was loading an assembly in child application that already exist in parent application. When I loaded with AppDomain.CurrentDomain.GetAssemblies() I got the assembly and resolved the Loader problem in GetTypes(). Thanks a lot :) @Dan Dryant – Sarath Subramanian Nov 18 '14 at 07:24
  • 1
    Absolute lifesaver... this resolved all the missing type voodoo I was experiencing from dynamically loaded assemblies. Never in a million years would I expect the solution to be *this*. – TiberiumFusion May 17 '20 at 03:39
  • I second your awesomeness. Been looking for a solution like this for years. Have a plugin system where their libraries and dependencies are kept in a separate folder and loaded at runtime. This resolved the issue. Simple and effective. Thanks. – Michael Feb 09 '23 at 13:32
2

Thanks to this post I could solve the ReflectionTypeLoadException that I was getting in a UITypeEditor. It's a designer assembly (a winforms smart-tag used at design-time) of a custom class library, that scan for some types.

/// <summary>
/// Get the types defined in the RootComponent.
/// </summary>
private List<Type> getAssemblyTypes(IServiceProvider provider)
{
    var types = new List<Type>();
    try
    {
        IDesignerHost host = (IDesignerHost)provider.GetService(typeof(IDesignerHost));
        ITypeResolutionService resolution = (ITypeResolutionService)provider.GetService(typeof(ITypeResolutionService));
        AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
        {
            foreach (var assembly in ((AppDomain)sender).GetAssemblies())
            {
                if (assembly.FullName == args.Name)
                {
                    return assembly;
                }
            }

            return null;
        };

        Type rootComponentType = resolution.GetType(host.RootComponentClassName, false);
        types = rootComponentType.Assembly.GetTypes().ToList();
    }
    catch
    {
    }

    return types;
}
Gustavo Mori
  • 8,319
  • 3
  • 38
  • 52
0

You are getting an assembly version mismatch. Since your plugins refer to this libPublic.dll, you must version it carefully and in particular not bump its revision/build/etc. numbers at every compile.

Anton Tykhyy
  • 19,370
  • 5
  • 54
  • 56