Dynamic Dependency Loading for Plugins

I’m creating am OpenTAP plugin for a new Keysight application, and what I want to do is instead of try to ship the application with the plugin or ship the plugin with the required dependency assemblies, I want to be able to load the dependencies dynamically from the application installation directory. Conveniently, this path is an environment variable, so I know the location of the dependencies, but my problem is that I have classes, including Instrument classes, that implement interfaces from the dependencies, so the dependency needs to be loaded before anything else in the plugin.

@brennen_direnzo suggested adding a ComponentSetting, but

  1. this shows up in the UI and
  2. it’s not actually loaded before the Bench Settings, it’s loaded concurrently, so the dependency isn’t actually loaded before the constructor for the Instrument is called.

And I can’t load the dependency in the constructor for the Instrument, because again the dependency needs to be loaded before the constructor is ever called. Is there a way to get an entry point to the plugin before anything else in the plugin is loaded?

Maybe @rolf_madsen do you know?

3 Likes

Hi @john.berlien, if you know where those assemblies are located, you can add an assembly resolver callback:

AppDomain.CurrentDomain.AssemblyResolve += (s, args) => findMyAssembly(args.Name);

This way .NET will be able to find those interface dependencies you have.

@rolf_madsen thanks, yes that’s one idea for getting the assemblies, but what I really need is to know where that code goes. Whether I load the assemblies manually, or add the assembly resolved event handler, is there an entry point into the plugin where that code is guaranteed to get called first before any other code in the plugin?

@john.berlien, hmm OK, maybe I don’t quite understand the problem then.

As I read your question you have some DLLs that you need to load before loading your plugin DLLs, right?

If this is the case, AssemblyResolve will be invoked just before the DLL is needed, so you have a chance to resolve the assembly before .NET tries (and maybe fails).

Yes but the problem is I don’t even have a chance to attach that event handler before say my Instrument constructor is called, if I already have an Instrument of my custom type in the Bench. So I have the plugin, the question is can I add anything to the plugin, a class, something that would guaranteed have code that gets called before any Instrument or TestStep constructors?

Uh, in this case it’s more complicated. Maybe first try a static constructor. I am not sure if that requires interface DLLs to be loaded.

If that fails, you can try using module initializers. Officially this is a .NET 6 feature, but since it is not part of .netstandard 2.0 you will have to use something like Fody to get it to work: NuGet Gallery | ModuleInit.Fody 2.1.1

I did try static constructor of a static class, but this doesn’t seem to get called at all when the plugin is loaded. And a static constructor for the Instrument or TestStep won’t work if you can’t even load the type, and you can’t load the type if the dependency is missing.

Okay I’ll look into this, see if it works.

This honestly also seems like something that would be simple to add in OpenTAP - another class in the Plugin whose constructor becomes the entry point for the plugin? Before any other types are loaded or other code is called? Maybe I’m wrong but seems like it would just be maybe another interface added and another few lines to PluginManager. Wouldn’t break anything either, as it would purely be an addition.

Okay it seems like this will not work. The module initializer is not called when the module is loaded, but when one of the types is attempted initialization. This doesn’t seem to apply to loading through Reflection, though, as it seems that’s what is done to load the Bench profile. Therefore, when it tries to get the Instrument Type on load, since the dependency isn’t there, it can’t load the Type. The module initializer is only called when a constructor is called., but it’s too late by then. Perhaps the best solution then would be to add an explicit Plugin entry point, as suggested in my last post, perhaps a class whose constructor or static constructor is called before any other types attempt loading. This to me would be a pretty easy-to-implemenent yet versatile solution.

@john.berlien

Have you considered a Settings file on your Visual Studio project?

If you right click on your project and select add new item, then you can select a settings file:

You will have a settings file where you can add a string with your environmental variable or an absolute path:

then you can access this during the constructor of your instrument:

    public MyInstrument1()
    {
        Name = "MyINST";
        // ToDo: Set default values for properties / settings.
        Log.Info("Required dependencies path: " + Settings1.Default.RequiredDependencies);
        Log.Info("Required dependencies absolute path: " + Settings1.Default.RequiredDepAbsolutePath);
    }

You can see it resolves the value and prints it during execution of the instrument constructor

Hope this helps you,

Carlos Montes

1 Like

Thanks very much for the info! I did not really know about Settings files, so thanks for showing me. However, the issue is that the Instrument implements an Interface from the dependency, so the Type itself of the Instrument can’t be loaded before the dependency gets loaded, hence the idea of having some code that loads before anything else in the plugin gets touched.

@john.berlien it looks like you started working on a new feature to address this. When you get it implemented can you share an example?

1 Like

Yes I can! It’s almost merged into the master branch, so it should be available in beta soon, and then I can share an example.

1 Like

Interesting thread. I may be out of my depth here, but… How about creating a standalone executable that copies the required dependency dlls from their installation directory, into the plugin binary directory. Then invoke that exe as PackageActionExtensions in package.xml. So whenever the package is installed, the required dlls get copied to the right place.

  <PackageActionExtensions>
    <ActionStep ExeFile="MyPlugin\CopyDependencies.exe" ActionName="install"></ActionStep>
  </PackageActionExtensions>

So to be honest, if I just couldn’t get the dynamic loading, I was going to just copy the dlls somehow through the plugin itself. But I still need this feature. There are a couple things I was considering:

  1. As stated above, I don’t want to ship the plugin with the product or worse ship the dlls with the plugin. Both are messy and should be unnecessary. Plus, especially with shipping the dlls with the plugin, you could get two different versions of the application basically in two different locations on one machine.
  2. I’d be against copying the dlls only once when the package is installed again to avoid two different versions of the app on the same machine. Especially if there’s a new feature in an updated application, that update would not get carried over if the plugin isn’t updated, and if there’s no change to the plugin, why would it get updated? Therefore I’d want to always reference the newest version of the application.
  3. Even with copying the dlls through the plugin, you’d still want to do this before any other plugin code is run, again if you have a type that is dependent on (inherits from) another assembly. If that fails, you get no more chances and it’s toast. And the dependency has to be in place (resolved somehow) before the type is loaded. Therefore you’d still want this feature, whether you copy the dlls or dynamically load the assembly through Reflection (Assembly.LoadFrom) or handle the assembly resolution event.

Plus, I already implemented the feature. Open-source FTW.

2 Likes

Hi @brennen_direnzo sorry for the long, long delay in getting an example out, but here it is: john-b-at-keysight/OpenTap.Plugins.PreLoadCode: An example showing the OpenTAP feature of adding code to a plugin that will run right before the plugin is loaded (github.com)

There is one issue I’m currently investigating, where the Plugin Init Method throwing an Exception is supposed to prevent the plugin from being loaded, and that’s not happening.

2 Likes