views:

149

answers:

3

Hello, 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);
            }
        }
    }
A: 

I'm not certain to why this is occurring, but when using the 'as' type of casting you don't get any information when the cast fails.

The MSDN documentation says: "The as operator is similar to a cast operation; however, if the conversion is not possible, as returns null instead of raising an exception."

So try using:

ICustomPlugin customPlugin = (ICustomPlugin)plugin;

and see if there is some information in the exception that can help explain why.

Fuzz
"Unable to cast object of type 'CustomPlugin' to 'ICustomPlugin'"
Slantroph
A: 

I suspect that there are two different ICustomPlugin types in play here. This is possible, for example, if you're doing strong-name signing, and CustomPlugin.dll is loading a different version of CustomPlugin.Definition.dll than Host.exe is loading.

Stephen Cleary
dlls are not signed. and they load the same dll from same folder.
Slantroph
I agree, this line is very relevant: plugin = LoadPluginAndCreateAnInstanceSomehow(); Please share what that method is doing.
Kirk Woll
What do you get for `typeof(ICustomPlugin).AssemblyQualifiedName` when evaluated within Host.exe compared with CustomPlugin.dll?
Stephen Cleary
Assembly ass = Assembly.LoadFrom(filename); Type t = ass.GetType(ass.FullName + ".CustomPlugin"); plugin = (IPlugin)Activator.CreateInstance(t); this is a summary of what LoadPluginAndCreateAnInstanceSomehow() does. but i think nothing wrong with this method. because it doesn't give errors and yields an instance of IPlugin successfully.
Slantroph
typeof(ICustomPlugin).AssemblyQualifiedName returned the same string:"CommonNamespace.Definition.INetwork, CommonNamespace.Definition, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
Slantroph
+2  A: 

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.

Sam
i deleted everything (bin, lib, tmp. obj, etc..) and built the solution from the scratch. so there was not gonna be any old/new version issue. besides now I'm loading plugings into CurrentAppDomain. the problem is still there :/
Slantroph
Did you try looking at plugin.GetType().Assembly.Location for each plugin?
Sam
I set "Copy Local" properties of references to false. changed some output paths. and now it works like a charm. thanks!
Slantroph