3

I have a solution with 3 projects: 1) A GUI executable 2) A Class library with an containing a public API and a public interface 3) A Class library of a class that implements the above interface

I'm trying to implement a resource loader in the API, so that when the GUI calls method API.Foo() I go over every assembly inside a specific folder (found at: .\resources) which contains a copy of the assemblies I compiled (#3). Then I want to add the resource to a list and use this list to call a function that is a part of the interface (which every resource implements)

So what I've done is:

private List<IResource> m_resources;

public void Foo()
{
    string resourceDir = Directory.GetCurrentDirectory() + @"\Resources";
    m_resources= new List<IResource>();
    foreach (var dllFile in  Directory.EnumerateFiles(resourceDir))
    {
        IResource dllInstance;
        if (TryLoadingDLL(Path.Combine(resourceDir, dllFile), out dllInstance))
        {
            resources.Add(dllInstance);
        }
    }
}

private static bool TryLoadingDLL(string dllFullPath, out  IResource instanceOfDll)
{
    instanceOfDll = null;
    try
    {
        Assembly assembly = Assembly.LoadFrom(dllFullPath);
        Assembly IResourceAssambly = Assembly.LoadFrom(@"C:\MyProject\MyAPI.dll");
        Type[] types = assembly.GetTypes();
        foreach (Type type in types)
        {
            var interfaces = type.GetInterfaces();
            var typeOfIResource = IResourceAssambly.GetType("MyProject.IResource");
            if (interfaces.Any())
            {
                var interfaceType = interfaces[0]; //In debuger they have the same GUID
                if (interfaceType.IsEquivalentTo(typeOfIResource)) //also tried ==
                {
                    instanceOfDll = Activator.CreateInstance(type) as IResource;
                    return true;
                }
            }
        }
    }
    catch (Exception e)
    {
        Console.Error.WriteLine("Failed to load dll {0}, exception: {1}",dllFullPath, e.Message);
        return false;
    }
    return false;
}

Iv'e actually used this at first which gave the same result:

List<Type> derivedTypesList = typses.Where(type => type.GetInterfaces().Contains(IWNAssambly.GetType("MyProject.IResource"))).ToList();
if (derivedTypesList.Count > 0)
{
    instanceOfDll = (IResource)Activator.CreateInstance(derivedTypesList[0]);
    return true;
}

but then I broke it down so I can debug it easily.

When I run any of these snippets, I Indeed find 1 type that implements the interface, but when I try to cast it I get null via the as operator and an exception when casting with (IResource). The exception is:

{System.InvalidCastException: Unable to cast object of type 'MyProject.MyFirstResource' to type 'MyProject.IResource'.

at ...

The problem looked like it was coming from the types so I tried replacing

var typeOfIResource = IResourceAssambly.GetType("MyProject.IResource");

with

var typeOfIResource = typeof(MyProject.IResource);

And the result was that now it didn't find anything at all, i.e the interfaceType.IsEquivalentTo(typeOfIResource) is always false. When I looked with the debugger on these types they looked exactly the same so I don't know what's the problem.

First, is this a good practice? I want other developers to supply me with their assemblies and if they implement the IResource interface then use reflection to create an instance and invoke the wanted method.

Second and more important at this time, what is the problem and how can I solve it?

Thanks!!!

ZivS
  • 2,094
  • 2
  • 27
  • 48
  • 1
    Any particular reason you aren't just using `MEF` for this? – BradleyDotNET Jan 21 '15 at 00:47
  • I'm not familiar with MEF. The reason we wanted this design is scalability. The assemblies that will be added in the future will be our own, they represent different ways of calculating the same thing, and after all the ways returned an answer, we mix the results somehow. – ZivS Jan 21 '15 at 01:00
  • 2
    Okay, here's a dumb question. Do they actually reference the *same interface* (in some common assembly) or do they simply have a copy of the file? I would look into MEF though, way easier. – BradleyDotNET Jan 21 '15 at 01:18
  • I will look into it, thanks. And they are all under the same solution, referencing from inside the solution, however I think they use a local copy :/ will update soon. – ZivS Jan 21 '15 at 01:35
  • Well this is awkward...I did have the copy local flag on :| @BradleyDotNet, Thank you for your dumb question :P – ZivS Jan 21 '15 at 02:16

2 Answers2

6

This brings back memories; I ran into the same problem in the very first .NET program I ever wrote, which must have been fifteen years ago now.

The problem is that .NET has different "binding contexts" in which a type can be loaded, and "Load" and "LoadFrom" load into different contexts. The "same" type in two different contexts will be treated as different by the runtime, and you cannot cast between them.

This is a fairly frequently asked question on Stack Overflow; if you do a search for those terms you should find some examples of explanations and possible solutions. For example, this one:

Create object from dynamically load assembly and cast it to interface (.NET 2.0)

Also, this blog article from the early days of .NET might help explain the design

http://blogs.msdn.com/b/suzcook/archive/2003/09/19/loadfile-vs-loadfrom.aspx

Finally, the other answer is correct; if you are looking to build a plugin system, I recommend against doing it from scratch. Use MEF or MAF or some other system designed specifically to solve your problem. Loading assemblies may be the least of your worries; suppose you must live in a world where third-party plugins might be hostile? Solving that security problem is difficult, so let someone else do it for you.

Community
  • 1
  • 1
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
1

Look system.component, since .NET 4 the framework MEF have been integrated. MEF

This framework permits you to flag by an attribute [Export(typeof(interface))] on every implementation of the interface regardless the dll and to load them using a catalog system. (There is one for the folder DirectoryCatalog

Mickael Thumerel
  • 516
  • 4
  • 14