I have got a problem on casting an object to one of it’s base interfaces living in another library. Here is the code for it:
BaseSDK.dll
public interface IPlugin
{
void Run();
}
CustomPlugin.Definition.dll:
public interface ICustomPlugin
{
void DoCustomStuff();
}
CustomPlugin.dll (has reference to BaseSDK.dll and CustomPlugin.Definition.dll):
public class CustomPlugin: IPlugin, ICustomPlugin
{
public void Run()
{
}
public void DoCustomStuff()
{
}
}
Host.exe (has references to BaseSDK.dll and CustomPlugin.Definition.dll):
IPlugin plugin;
public void DoStuff()
{
plugin = LoadPluginAndCreateAnInstanceSomehow();
// I know plugin is a CustomPlugin
ICustomPlugin customPlugin = plugin as ICustomPlugin; //cast fails.
customPlugin.DoCustomStuff();
}
I don’t understand; this is just plain type-casting a type to it’s base type. How can i fix this? or any alternatives?
Edit: Here is a summary of what LoadPluginAndCreateAnInstanceSomehow() does:
Assembly ass = Assembly.LoadFrom(filename);
Type t = ass.GetType(ass.FullName + ".CustomPlugin");
plugin = (IPlugin)Activator.CreateInstance(t);
Edit 2: Plugins are loaded into another AppDomain. I had to add IPlugin and ICustomPlugin.Definiton as reference at compile-time because the app must have a clue about what an CustomPlugin is like. is this the source of the problem? if so, how can I achieve what I’m trying to do?
Edit 3: Plugins are loaded like this:
public class PluginLoader
{
List<IPlugin> Plugins;
AppDomain Domain;
string libPath;
public void PluginLoader(string sLibPath, AppDomain sDomain)
{
libPath = sLibPath;
Plugins = new List<IPlugin>();
Domain = sDomain;
if(Domain==null)Domain = AppDomain.CreateDomain("PluginsDomain");
Domain.AssemblyResolve += new ResolveEventHandler(Domain_AssemblyResolve);
Domain.TypeResolve += new ResolveEventHandler(Domain_TypeResolve);
Domain.DoCallBack(new CrossAppDomainDelegate(DomainCallback));
}
Assembly Domain_AssemblyResolve(object sender, ResolveEventArgs args)
{
return Assembly.LoadFrom(args.Name);
}
Assembly Domain_TypeResolve(object sender, ResolveEventArgs args)
{
return Assembly.LoadFrom(args.Name);
}
void DomainCallback()
{
string[] fileNames = Directory.GetFiles(libPath, "*.dll");
foreach (string filename in fileNames)
{
FileInfo fi = new FileInfo(filename);
Assembly ass = Assembly.LoadFrom(fi.FullName);
Type t = ass.GetType(ass.FullName + ".CustomPlugin");
IPlugin p = (IPlugin)Activator.CreateInstance(t);
Plugins.Add(p);
}
}
}
I managed to reproduce this issue. Consider the following.
Assume your project has the following runtime structure (simplified of course)
C:\Runtime\ – this is your main runtime directory and has Host.exe
C:\Runtime\Library\ – this path has your three library assemblies
The exe project references the two assemblies that define the interface, so they are copied to the runtime folder, but does not reference the assembly that contains CustomPlugin, so it is not copied.
What if an older version of the project DID reference CustomPlugin.dll and copied an old version to \Runtime? Then later on you separated these. The new version is no longer copied, but the old version remains.
So when you create the class in the AppDomain from \Library, all is ok, but when marshalled back over the AppDomain boundary, the primary AppDomain needs to load the matching assembly to get the type information. It finds the old version first. If the old version does not implement ICustomConfig, then the cast will fail. You mentioned earlier that they are not signed, so .NET would have no problem making this mistake for you!
So your runtime directory contains
Host.exe
BaseSDK.dll
CustomPlugin.Definition.dll
CustomPlugin.dll – older version not implementing ICustomPlugin
And your library directory contains
BaseSDK.dll
CustomPlugin.Definition.dll
CustomPlugin.dll – new version
EDIT
You could always check the value of plugin.GetType().Assembly.Location to see which dll it is loading in each AppDomain.