AppDomain.CurrentDomain.AssemblyResolve does not fire while loading plug-in for main software. Why?

StackOverflow https://stackoverflow.com/questions/19177399

  •  30-06-2022
  •  | 
  •  

Question

The plug-in (let us call it PLUGIN) I am developing is using two assembly files from the main software (let as call it PARENT) it is written for. As soon as the PARENT is updated to a new version (and it happens several times a week) I want my PLUGIN to load the new versions of the dependencies dynamically without forcing me to re-compile.

The PARENT loads its plug-ins as source code files and compiles them just in time. Since I want my code to be in a DLL my Loader.cs file calls functions from my DLL via reflection.

The following is the code of the Loader.cs.

// error handling removed for better readability

public Loader()
{   
    assembly = Assembly.LoadFile(dllPath);
    type = assembly.GetType("PLUGIN.PLUGINStarter");
    instance = Activator.CreateInstance(type);
}

public override void Dispose()
{
    type.InvokeMember("Dispose", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, instance, null);
    base.Dispose();
}

public override void OnButtonPress()
{   
    type.InvokeMember("ShowForm", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, instance, null);
}

Now the PLUGINStarter class in PLUGIN namespace looks as follows.

class PLUGINStarter
{
    private PLUGIN plugin = null;

    /// <summary>
    /// This is loading PARENT.exe and PARENTSomeOtherFile.dll dependencies.
    /// </summary>
    public PLUGINStarter()
    {
        AppDomain.CurrentDomain.AssemblyResolve += (sender, eventArgs) =>
        {
            var fullAssemblyName = new AssemblyName(eventArgs.Name);              

            // this is not executed when PARENT version changes
            MessageBox.Show(fullAssemblyName.Name, "Loading assembly...");

            if (fullAssemblyName.Name.Equals("PARENT"))
            {
                // AppDomain.CurrentDomain.FriendlyName will handle the case where PARENT.exe is re-named
                var found = Assembly.LoadFile(Path.Combine(Environment.CurrentDirectory, AppDomain.CurrentDomain.FriendlyName));
                return found;
            }
            else if (fullAssemblyName.Name.Equals("PARENTSomeOtherFile"))
            {
                var found = Assembly.LoadFile(Path.Combine(Environment.CurrentDirectory, "PARENTSomeOtherFile.dll"));
                return found;
            }
            else
            {
                return null;
            }
        };

        Initialize();
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private void Initialize() 
    {
        // the PARENT's assemblies are referenced in the PLUGIN class
        plugin = new PLUGIN();
    }

    public void ShowForm()
    {
        plugin.ShowForm();
    }

    public void Dispose()
    {
        plugin.Dispose();
    }
}

When the PARENT is updated to a new version the event is not fired. Why?

EDIT #1

For clarification: PARENT loads (compiles it just in time) Loader.cs which loads the PLUGIN.dll which depends on assemblies from PARENT (and mainly PARENT.exe itself).

EDIT #2

The PARENT software is updated manually. The user downloads it from the internet (the web site of the product). Then the user copies my PLUGIN.dll into the "Plugins" directory of the PARENT.

Then I am able to catch the following exception in Loader.cs.

[07:55:19.822 D] [PLUGIN] Loading DLL 'C:\PNT\PARENTNew\Plugins\PLUGIN\PLUGIN.dll'.
[07:55:19.826 D] [PLUGIN] Exception loading assembly 'System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.IO.FileLoadException: Could not load file or assembly 'PARENT, Version=6.2.8113.191, Culture=neutral, PublicKeyToken=21a554ab5c01ae50' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
   at PLUGIN.PLUGINStarter..ctor()
   --- End of inner exception stack trace ---
   at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
   at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
   at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
   at System.Activator.CreateInstance(Type type, Boolean nonPublic)
   at System.Activator.CreateInstance(Type type)
   at PLUGINLoader.Loader..ctor() in c:\PNT\PARENTNew\Plugins\PLUGIN\Loader.cs:line 42'.

EDIT #3

I believe this is possible as described in Is it possible to replace a reference to a strongly-named assembly with a "weak" reference? but for some strange reason it fails in my application.

EDIT #4

I solved the problem by removing PLUGINStarter and moving the assembly resolving code to the constructor of Loader.cs. Now everything resolves nicely despite wrong assembly versions.

Was it helpful?

Solution 2

As described in EDIT #4 the assembly event was not registered soon enough.

I solved the problem by removing PLUGINStarter and moving the assembly resolving code to the constructor of Loader.cs. Now everything resolves nicely despite wrong assembly versions.

OTHER TIPS

It's getting to long for a comment, so I place it as an answer.

I still don't understand why do you load a PARENT assembly that is already loaded or how do you "update" PARENT...

If a dll is loaded there is generally no way to unload it. You must create a new AppDomain to make it work.

So as I see it, you run your PARENT, it loads the PLUGIN, then the event is attached.

According to http://msdn.microsoft.com/en-us//library/ff527268.aspx

When you register a handler for the AssemblyResolve event, the handler is invoked whenever the runtime fails to bind to an assembly by name. For example, calling the following methods from user code can cause the AssemblyResolve event to be raised:

An AppDomain.Load method overload or Assembly.Load method overload whose first argument is a string that represents the display name of the assembly to load (that is, the string returned by the Assembly.FullName property).

An AppDomain.Load method overload or Assembly.Load method overload whose first argument is an AssemblyName object that identifies the assembly to load.

An Assembly.LoadWithPartialName method overload.

An AppDomain.CreateInstance or AppDomain.CreateInstanceAndUnwrap method overload that instantiates an object in another application domain.

That means you have to explicitly load an assembly (and fail to do it automatically) to fire the event.

So I believe the "updating" of PARENT is crucial here. How do you do it?

EDIT

According to your edits, I believe your answer can be found here: If I rebuild a dll that my project references, do I have to rebuild the project also?

This link also mentions something about strict forcing of referenced assembly's version and how to avoid it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top