Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problem with creating instances of type using reflection from strong-named assembly

I have an existing .NET 2.0 Win Forms solution that's deployed on the client. For the sake of this scenario, lets call it 'WinApp.exe'. WinApp.exe has a direct reference to a private assembly named 'Assembly.dll'. Assembly.dll has a direct reference to another assembly named 'Framework.dll'. Within Framework.dll is a type named 'IPlugIn'. Framework.dll is not strong-named, but the version of the assembly is 1.0. There is a class in Assembly.dll that implements IPlugIn from Framework.dll.

I have another .NET 2.0 Win Forms solution named 'Framework.exe'. This Win Forms project also has a direct reference to Framework.dll, which has the 'IPlugIn' type defined. Framework.dll is not strong-named, but the version of this assembly is 2.0. Bit for bit, 'IPlugIn' in Framework.dll v2 is identical to 'IPlugIn' in Framework.dll v1. The code in Framework.exe uses reflection to load the implementation of IPlugIn that's in WinApp:

AssemblyName an = AssemblyName.GetAssemblyName("C:\\Program Files\\WinApp\\Assembly.dll");
Assembly dll = Assembly.Load(an);
object o = Activator.CreateInstance(dll.GetType("WinApp.ClassThatImplementsIPlugIn"));
IPlugIn iPlugIn = o as IPlugIn;

So far, so good. This code works! Now here's the troublesome part. I need to assign a strong name to Framework.dll v2, so that it can be put in the GAC. However, WinApp and its dependent assemblies will not be redeployed - it must continue to use the same versions of assemblies that it's currently using. When I give Framework.dll v2 a strong name and recompile and run Framework.exe, when the above code executes I receive a "InvalidCastException" on line:

IPlugIn iPlugIn = o as IPlugIn;

I think I'm receiving this exception because now that one version of Framework.dll has a strong name, the runtime is treating the type "IPlugIn" as if it's a different type altogether. I need to know if there's any way to solve this issue. Again, the requirements are:

  1. I must be able to put Framework.dll v2 in the GAC (therefore it must have a strong name).
  2. WinApp must continue to work as it currently does (continue to reference Framework.dll v1). I cannot redistribute newly compiled assemblies for WinApp.

Thanks in advance!

Chad

like image 669
Chad Ernst Avatar asked Oct 11 '22 20:10

Chad Ernst


1 Answers

The problem here is that both the strong named Framework as the old Framework assembly are loaded. .NET does not really care that both definitions of IPlugin are the same. They are from a differend assembly so they are different (I am a little puzzled as why it will raise an InvalidCastException as a cast like that will just returns null if failed).

option A

One method to still use the class will be to use reflection but this might go a long way when more types hosted in Framework come to play. all of these will need to be accessed using reflection. You can create wrappers to hide the reflection using something like this:

public class PluginWrapper : IPlugin
{
  object fObj;
  PropertyInfo fNameProperty;
  MethodInfo fGetOtherMethod;

  public PluginWrapper(object o)
  {
    fObj = o;
    fNameProperty = o.GetType().GetInterface("IPlugin").GetProperty("Name");
    fGetOtherMethod = o.GetType().GetInterface("IPlugin").GetMethod("GetOther", new Type[] { typeof(string) });
  }

  public string Name
  {
    get { return (string)fNameProperty.GetValue(fObj, null); }
  }

  public IOther GetOther(string name)
  {
    object result = fGetOtherMethod.Invoke(fObj, new object[] { name });

    if (result == null)
      return null;

    return new OtherWrapper(result);
  }
}

Then you can use your object as following:

IPlugin iPlugIn = new PluginWrapper(o);

option B

I can think of another way. I am not sure if this a road you should go as it looks a lot of "hacking" in my eyes but i'll share it anyway and let you decide.

You can let the assembly load using a byte stream instead of AssemblyName. That way it will be unable to resolve the other Framework assembly and giving you a change to break in on the AssemblyResolve event:

AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
Assembly dll = Assembly.Load(File.ReadAllBytes(@"C:\Program Files\WinApp\Assembly.dll"));
object o = Activator.CreateInstance(dll.GetType("WinApp.ClassThatImplementsIPlugIn"));
IPlugin iPlugIn = o as IPlugin;

static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
  if (args.Name.ToLower().StartsWith("framework,"))
    return typeof(IPlugin ).Assembly;

  return null;
}

This might be a little tricky as by that you will give .NET a promise that the assembly is compatible and that you are prepared to take the risk. If things are not compatible then problems might quickly start to arise.

Other options

There might maybe also be ways to solve the problem using bindingredirect element in app.config. I'm not really known with the details about how that works but might be worth looking into a bit.

like image 92
Jan-Peter Vos Avatar answered Oct 14 '22 05:10

Jan-Peter Vos