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.
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?
@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.
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.
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.
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.
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:
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.
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.
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.
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.