2

I have an application where I load plugins by reading their DLL file and then loading the bytes using AppDomain.CurrentDomain.Load(bytes). Note that the application and the plugin are loaded in the same AppDomain. The plugin contains several classes which register themselves in a service locator system using a static constructor.

Later, my main application tries to find and instantiate one of these service classes using the service locator, but it cannot find the class. Upon manual inspection, I can see that the registry entry is present in the locator, so it was registered, but for some unknown reason the types aren't equal.

I then put a breakpoint at the place where the type is registered and discovered the following weirdness:

Screenshot from in debugging

How can typeof(IViewFor<CompactDashboardViewModel>) not be equal to itself?

I then tested a few more things:

t == t
true
typeof(IViewFor<CompactDashboardViewModel>) == typeof(IViewFor<CompactDashboardViewModel>)
true
t.AssemblyQualifiedName == typeof(IViewFor<CompactDashboardViewModel>).AssemblyQualifiedName
true

In fact, everything about these 2 Type objects seems to be equal, except the m_handle and m_cache fields.

typeof(IViewFor<CompactDashboardViewModel>).TypeHandle
{System.RuntimeTypeHandle}
    Value: 0x08690784
    m_type: {Name = "IViewFor`1" FullName = "ReactiveUI.IViewFor`1[[PluginMTSICS.ViewModel.CompactDashboardViewModel, PluginMTSICS, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]"}
t.TypeHandle
{System.RuntimeTypeHandle}
    Value: 0x0f8cf5a8
    m_type: {Name = "IViewFor`1" FullName = "ReactiveUI.IViewFor`1[[PluginMTSICS.ViewModel.CompactDashboardViewModel, PluginMTSICS, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]"}

Does anybody know what is happening here? I am using .NET 4.7.1. I am trying to create an MCVE, but unsuccessfully so far.

Wouter
  • 538
  • 6
  • 15
  • 3
    Perhaps you have multiple assemblies, different versions? – Lasse V. Karlsen Aug 03 '18 at 12:46
  • == is not a valid comparison for reference types, you should use Equals method. – Hozikimaru Aug 03 '18 at 12:58
  • I have put a breakpoint on the DLL loading code, and it is only run once, so I don't think there can be multiple assemblies. Also: If you look at the last snippet with the FullName of the two types, the assembly name and version are identical. – Wouter Aug 03 '18 at 13:07
  • 1
    https://stackoverflow.com/a/3624044/1462295 says "The same class / type loaded by different app domains [.NET] or class loaders [Java] will not compare equal and are not assignable to/from each other directly." – BurnsBA Aug 03 '18 at 13:18
  • Yes, I read that too, but I specifically load the DLL into `AppDomain.CurrentDomain`, so I don't see how that could cause it? – Wouter Aug 03 '18 at 13:20
  • 2
    This _smells_ a lot like the same DLL is loaded twice. That can happen under various circumstances - unfortunately it's up to you to figure out just what they are in this case. – Vilx- Aug 03 '18 at 13:22
  • Check the Loaded Modules window in the debugger, and inspect not only `AssemblyQualifiedName` but the `Assembly` objects themselves for each type. There should be a difference there somewhere. – Vilx- Aug 03 '18 at 13:23
  • Most of the time it will be because the same DLL will be loaded in different ways. You already say that you normally load it by `AppDomain.CurrentDomain.Load(bytes)` - that leaves the door wide open for loading it in another way, like the default module loader which tries to locate and load assemblies when an unknown type is encountered. – Vilx- Aug 03 '18 at 13:27
  • I just checked the Modules window, and there is only one entry for the plugin. The modules window also confirms that all assemblies are loaded into one and the same AppDomain. – Wouter Aug 03 '18 at 13:39
  • Type identity problems are a notorious issue in the current managed debugging engine. Get another opinion with Tools > Options > Debugging > General, tick "Use Managed Compatibility Mode". And check your code, you have to be sure that Assembly.Load(byte[]) only runs once. Issues with the loading context are pretty tricky problems to debug. Otherwise a very good reason to avoid it. – Hans Passant Aug 03 '18 at 13:39
  • "Use Managed Compatibility Mode" does not seem to have any effect. I have checked and double checked, the assembly loading code only gets called once, and this is also additionally confirmed by the Modules window. – Wouter Aug 03 '18 at 13:50
  • Hmm... Another avenue to explore then - maybe it's the Immediate Window itself? I've never used it personally. Does the code in it run in the same AppDomain? Perhaps the Immediate Window loads the assembly the second time? – Vilx- Aug 03 '18 at 14:16
  • Also - which type is it that differs? `IViewFor<>` or `CompactDashboardViewModel`? – Vilx- Aug 03 '18 at 14:17
  • I'm not fully aware of how the immediate window works, but I don't think it is the problem. I can put a breakpoint in the spot where the service lookup occurs and access both the type object that is in the dictionary, and the type object that is used to perform the lookup. (Note that these were both created in code, and so not in the immediate window) Comparing these using the immediate window produces the same result: not equal. – Wouter Aug 03 '18 at 14:33
  • `t.GetGenericTypeDefinition() == typeof(IViewFor<>)` returns `true`. Note that `IViewFor<>` is an interface that is part of a NuGet library which is used by both the main application and the plugin. – Wouter Aug 03 '18 at 14:38
  • So it's the `CompactDashboardViewModel`. Can you double check that? After that, can you take both type objects and compare their `Assembly` properties? – Vilx- Aug 03 '18 at 16:02
  • Interesting. So the `CompactDashboardViewModel` types are indeed not equal, because one is in the plugin assembly and the other somehow is supposed to be in the main application assembly. https://i.imgur.com/cBEBbIj.png – Wouter Aug 04 '18 at 10:01

2 Answers2

0

Maybe this works:

Type t = typeof(IViewFor<CompactDashboardViewModel>);
//this should evaluate to true:
bool result = t.Equals(typeof(IViewFor<CompactDashboardViewModel>));

Type.Equals docs: https://msdn.microsoft.com/en-us/library/3ahwab82(v=vs.110).aspx

EDIT:

After reading this post Type Checking: typeof, GetType, or is? i would expect this to work:

Type t = typeof(IViewFor<CompactDashboardViewModel>);
//this should evaluate to true:
bool result = t is IViewFor<CompactDashboardViewModel>;
Lennart
  • 752
  • 1
  • 5
  • 18
0

OK, so I fixed the issue. Here is what I did:

  1. My main application had a reference to a library project, which in turn referenced the plugin project. This probably caused the assembly to be loaded twice, in different load contexts (see links below for more info). I removed the reference. The problem was not fixed, and now weird stuff was happening such as typeof(CompactDashboardViewModel) == null.
  2. My plugin loading code originally used appdomain.Load(bytes). I replaced this with Assembly.LoadFrom. typeof() now worked correctly, and works as expected. However, Type.GetType() still returns null sometimes.
  3. I replaced Assembly.LoadFrom with Assembly.Load and added my plugin directory to the probing path using the <probing> tag in app.config. Everything works correctly now, however I can't load the plugins by filepath, as Assembly.Load requires the assembly name. Not ideal, but I can live with that.

Useful sources:

Wouter
  • 538
  • 6
  • 15