Alexandre Leites
Alexandre Leites

Reputation: 388

Loading External Assembly using reflection

I'm trying to develop an application that supports dynamic loading of external modules. I have read several articles from loading external assemblies using C# (.NET v4.5) and got the code below. However, it is not working, not detecting my subclass on the external module.

Here is the code for loading external assembly:

byte[] array = <HERE I LOAD THE DLL>
Assembly asb = Assembly.Load(array);
Type[] types = GetAssemblyTypes(asb);
for( int i = 0; i < types.Length; i++ )
{
    Type t = types[i];
    if( t != null && typeof(App).IsAssignableFrom(t) /*t.IsSubclassOf(typeof(App))*/ )
    {
        app.AppClass = (App)Activator.CreateInstance(t);
        return true;
    }
}

Here is the GetAssemblyTypes()

private Type[] GetAssemblyTypes(Assembly asb)
{
    Type[] types;
    try
    {
        types = asb.GetTypes();
    }
    catch( ReflectionTypeLoadException ex )
    {
        types = ex.Types;
    }

    return types;
}

Here is the class on the MAIN APPLICATION (This class will be used by the modules)

namespace MyApplication.API
{
    public class App
    {
       // CODE
    }
}

Here is the example of my module: using MyApplication.API;

namespace HelloWorld
{
    class HelloWorld : App
    {

    }
}

Important points are: 1 - I don't know the class name of the module, I just know that it will be a subclass of the App class.

The issue is that although the types.Length gives me 1, when I tries to access by types[i] it gives a null pointer. Am I missing something here?

Upvotes: 2

Views: 436

Answers (2)

Alexandre Leites
Alexandre Leites

Reputation: 388

I got a semi working version based on @CadBurry code.

byte[] bytes = <HERE I LOAD THE DLL>
Assembly asb = Assembly.Load(bytes);

AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += CurrentDomain_ReflectionOnlyAssemblyResolve;
IEnumerable<Type> types = asb.GetExportedTypes().Where(w => w.IsSubclassOf(typeof(App)));
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= CurrentDomain_ReflectionOnlyAssemblyResolve;
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;

if(types.Count() > 0)
{
    Type type = types.FirstOrDefault();

    if( type == null )
        return false;

    app.AppClass = (App)Activator.CreateInstance(type);
    return true;
}

with the methods:

private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    if( args.Name.Contains(typeof(MyApplication).Assembly.GetName().Name) )
    {
        return Assembly.GetExecutingAssembly();
    }
    return null;
}

private Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
{
    Assembly asb = AppDomain.CurrentDomain.GetAssemblies().Where(w => w.FullName == args.Name).FirstOrDefault();
    return asb;
}

Using the above code I can load the assembly even when it is not on the current directory of the main application (eg. Plugins folders).

Upvotes: 1

Cadburry
Cadburry

Reputation: 1864

I have done something similar to find out if the assembly is derived from my base assembly

    var assembly = System.Reflection.Assembly.LoadFrom(file);
                AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += CurrentDomain_ReflectionOnlyAssemblyResolve;
                var derivedAssemblies = assembly.GetExportedTypes().Where(w => w.IsSubclassOf(typeof(AddressManager.Base.Connector.ConnectorBase))).Count(); 
                AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= CurrentDomain_ReflectionOnlyAssemblyResolve;

                assembly = null;
                if (derivedAssemblies > 0)
                {
                    Manager.LoadAssembly(file, "Connectors");
                    Trace.TraceInformation(" Success! Library loaded.");
                }
                else
                    Trace.TraceInformation(" Skipped! Not a subclass of '" + typeof(AddressManager.Base.Connector.ConnectorBase).Name + "'.");

And handle the ReflectionOnlyAssemblyResolve Event:

   private System.Reflection.Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
    {
        var assembly = AppDomain.CurrentDomain.GetAssemblies().Where(w => w.FullName == args.Name).FirstOrDefault();
        return assembly;
    }

Upvotes: 2

Related Questions