I have a problem where I have a program that should load a plugin (DLL) from a specific directory where the DLL implements a specific base class. The problem is that my program that loads the DLL has a reference to an other DLL which the DLL being loaded also references. I will show an example of how the problem arises. This simple tests consists of 3 different solutions and 3 separate projects. NOTE: If I have all projects in the same solution, the problem does not arise.
Solution 1 - Project that defines the Base class and an interface AdapterBase.cs
namespace AdapterLib
{
public interface IAdapter
{
void PrintHello();
}
public abstract class AdapterBase
{
protected abstract IAdapter Adapter { get; }
public void PrintHello()
{
Adapter.PrintHello();
}
}
}
Solution 2 - Project that defines an implementation of the base class MyAdapter.cs
namespace MyAdapter
{
public class MyAdapter : AdapterBase
{
private IAdapter adapter;
protected override IAdapter Adapter
{
get { return adapter ?? (adapter = new ImplementedTestClass()); }
}
}
public class ImplementedTestClass : IAdapter
{
public void PrintHello()
{
Console.WriteLine("Hello beautiful worlds!");
}
}
}
Solution 3 - Main program which loads the DLL implementing AdapterBase* **Program.cs
namespace MyProgram {
internal class Program {
private static void Main(string[] args) {
AdapterBase adapter = LoadAdapterFromPath("C:\\test\\Adapter");
adapter.PrintHello();
}
public static AdapterBase LoadAdapterFromPath(string dir) {
string[] files = Directory.GetFiles(dir, "*.dll");
AdapterBase moduleToBeLoaded = null;
foreach (var file in files) {
Assembly assembly = Assembly.LoadFrom(file);
foreach (Type type in assembly.GetTypes()) {
if (type.IsSubclassOf(typeof(AdapterBase))) {
try {
moduleToBeLoaded =
assembly.CreateInstance(type.FullName, false, BindingFlags.CreateInstance, null, null,
null, null) as AdapterBase;
} catch (Exception ex) {
}
if (moduleToBeLoaded != null) {
return moduleToBeLoaded;
}
}
}
}
return moduleToBeLoaded;
}
}
}
So now the main program MyProgram.cs will try to load the DLL from path C:\test\Adapter and this works fine if I ONLY put the file MyAdapter.dll in that folder. However Solution 2 (MyAdapter.cs), will put both MyAdapter.dll and AdapterBase.dll in the output bin/ directory. Now if copy both those files to c:\test\Adapter the instance from the DLL does not get loaded since the comparison if (type.IsSubclassOf(typeof(AdapterBase))) { fails in MyProgram.cs.
Since MyProgram.cs already has a reference to AdapterBase.dll there seems to be some conflict in an other DLL gets loaded from a different path which references the same DLL. The loaded DLL seems to first resolve its dependencies against DLLs in the same folder. I think this has to do with assembly contexts and some problem with the LoadFrom method but I do not know how to get C# to realise that it actually is the same DLL which it already has loaded.
A solution is of course only to copy the only needed DLL but my program would be much more robust if it could handle that the other shared DLL also was there. I mean they are actually the same. So any solutions how to do this more robust?
Yes, this is a type identity problem. A .NET type's identity is not just the namespace and type name, it also includes the assembly it came from. Your plugin has a dependency on the assembly that contains IAdapter, when LoadFrom() loads the plugin it is also going to need that assembly. The CLR finds it in the LoadFrom context, in other words in the c:\test\adapter directory, normally highly desirable since that allows plugins to use their own DLL versions.
Just not in this case. This went wrong because you plugin solution dutifully copied the dependencies. Normally highly desirable, just not in this case.
You'll have to stop it from copying the IAdapter assembly:
Copy Local property to False.Copy Local is the essence, the rest of the bullets are there just to make sure that an old copy doesn't cause problems. Since the CLR can no longer find the IAdapter assembly the "easy way", it is forced to keep looking for it. Now finding it in the Load context, in other words, the directory where the host executable is installed. Already loaded, no need to load the one-and-only again. Problem solved.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With