Select to view content in your preferred language

Load ArcGIS Pro Add-Ins into their own AssemblyLoadContext

904
4
05-22-2024 01:18 PM
Status: Open
Labels (3)
KarlKemp
Occasional Contributor

In ArcGIS Pro 3, All Add-Ins (and Managed Configurations) appear to be loaded in the base .NET AssemblyLoadContext (AssemblyLoadContext.Default).  This means that all add-ins must use the same version of any other assemblies they reference.  This can cause crashes and other issues if two add ins require incompatible versions of the same assembly.

Add-Ins should each be loaded into their own AssemblyLoadContext instead of the default.  This would allow multiple add-ins that use different versions of the same reference assemblies to co-exist and run side-by-side more reliably.

 

 

Tags (3)
4 Comments
Mathijs_Dumon

I second this request. I'm now trying to wrap my own plugin into an AssemblyLoadContext to get around the problems of mismatched dll versions between ArcGIS Pro and my addin solution. It would make things a lot easier if we didn't need to do this ourselves.

Markus_Bedel
ChristophKoschmieder

We are currently facing the same Problem: For a group of customers, we developed a base add-in and in addition specific add-ins for each customer.

So the base add-in and the specific add-in are both installed and loaded in ArcGIS Pro.

The development of the add-ins is separated, so there are not always the same versions of e.g. log4net installed by nuget. Currently we manually have to sync those versions and if there are changes in the base add-in, we have to provide new add-in versions for the specific add-ins as well, even  if there are no changes done to them!

 @Mathijs_Dumon Could you please show me, how you realized the add-in specific AssemblyLoadContext in ArcGIS Pro?! Thanks in advance!

vanesch

This might be worth a try. I created a custom assembly resolver helper class to assist in loading conflicting assemblies. In the code below, a Module simply creates a new AssemblyResolver in its Initialize override. The helper class listens for assembly resolution failures and attempts to resolve the assemblies it is aware of.  You can still delay load (autoLoad="false") your module.

In version 3.5, we'll introduce a new feature that automates this process, though it will initially be available as a beta feature, accessible through a registry key. (HKEY_CURRENT_USER\Software\ESRI\ArcGISPro\Settings  Dword SideBySideAddIns = 1)

 

 

internal class Module1 : Module
{
  private static Module1 _this = null;
  private AssemblyResolver _assemblyResolver;
  protected override bool Initialize()
  {
    _assemblyResolver = new AssemblyResolver();
    return true;
  }
}


internal class AssemblyResolver
{
  private HashSet<string> _assemblyNames = new HashSet<string>();
  private string _addinFolder = string.Empty;

  internal AssemblyResolver()
  {
    Initialize();
  }

  private void Initialize()
  {
    // Hook into the default assembly resolver
    System.Runtime.Loader.AssemblyLoadContext.Default.Resolving += Default_Resolving;

    _addinFolder = System.Reflection.Assembly.GetExecutingAssembly().Location;
    _addinFolder = System.IO.Path.GetDirectoryName(_addinFolder);

    string[] assemblyFiles = Directory.GetFiles(_addinFolder, "*.dll", SearchOption.TopDirectoryOnly);

    // Create a list of the assembly dependencies
    foreach (var file in assemblyFiles)
    {
      try
      {
        System.Reflection.AssemblyName dependency = System.Reflection.AssemblyName.GetAssemblyName(file);
        _assemblyNames.Add(dependency.FullName);
      }
      catch { }
    }
  }

  private System.Reflection.Assembly Default_Resolving(System.Runtime.Loader.AssemblyLoadContext arg1, System.Reflection.AssemblyName assemblyName)
  {
    // Check if one of my assemblies is being looked for
    if (_assemblyNames.Contains(assemblyName.FullName))
    {
      try
      {
        AddInLoadContext addInLoadContext = new AddInLoadContext();
        string path = System.IO.Path.Combine(_addinFolder, assemblyName.Name + ".dll");
        var assembly = addInLoadContext.LoadFromAssemblyPath(path);
        return assembly;
      }
      catch { }
    }
    return null;
  }
}

internal class AddInLoadContext : System.Runtime.Loader.AssemblyLoadContext
{
  protected override System.Reflection.Assembly Load(System.Reflection.AssemblyName assemblyName)
  {
    return null;
  }
}