I’m completely new to OpenTap, but I’m very interested. During the last month I read trough many articles, forums, etc. and now I want to try OpenTap, together with the Test Developer Editor from Keysight.
I want to use that, to rebuild the companies homegrown production test environment which is completely written in C#, but a big mess.
I took a 30 days trail license and now I’m trying to build a plugin and run a testplan which uses the plugins instruments, teststeps, etc.
A first easy try was possible and then I wanted to add a dll which is protected with a private key.
In normal console, or windows forms appications I can put that private key to the startup code and run some assambly loader for that and afterwards I can use the dll functionality.
But in the Keysight Visual Studio project (to use the debugger) I cannot add this to the startup code, I think because it is a classlib and not a console application. So the dll still keeps locked and I cannot debug the plugins I want to build.
In the AssemblyLoder.Init method the public key will be extracted and the rsa object for the private key will initialized form the private key xml.
It will build the path to the assembly to load.
It will decrypt the assembly using the private key and the manufacturer ID.
It will load the assembly.
It will inject the public key for authentication.
This is done in the startup of the program. I don’t know how I can access the startup of the plugin project.
And also I need to inject this to the Keysight Editor, that I can use the plugins for Testsequence development.
If you have any idea, how that could work, let me know.
OK, so I guess its some kind of commercial licensing scheme for DLLs?
There is a slightly hidden attribute that allows you to run some code when the module gets discovered
It should work like this:
// in place of ModuleLoaderClass, use the fulle name. Eg NameSpaceName.ClassName.Init()
[assembly:PluginAssembly(false, "ModuleLoaderClass.Init()")]
public class ModuleLoaderClass
{
public static void Init(){
AssemblyLoader.Init(...)
}
}
You are right, it is some kind of protection scheme - for manufacturing of KNX products.
Where should I put that code?
If I insert a new class it is not executing it:
using OpenTap;
using System;
using System.Reflection;
// in place of ModuleLoaderClass, use the fulle name. Eg NameSpaceName.ClassName.Init()
[assembly: PluginAssembly(false, "KNX_Falcon.AssemblyInfo.Init()")]
namespace KNX_Falcon
{
public class AssemblyInfo
{
private static string s_privateKey = "<RSAKeyValue>...</RSAKeyValue>";
public static void Init()
{
AssemblyLoader.Init(s_privateKey);
}
}
}
I was looking into the OpenTAP source code that is supposed to call this module init function, and it seems like the class containing the init function must be static:
Type initClass = assembly.GetType($"{namespacePath}.{className}");
// Check if loaded class exists and is static (abstract and sealed) and is public
if (initClass == null || !initClass.IsClass || !initClass.IsAbstract || !initClass.IsSealed || !initClass.IsPublic)
throw new Exception($"Could not find method {fullName} in assembly: {Location}");
MethodInfo initMethod = initClass.GetMethod(methodName);
// Check if loaded method exists and is static and returns void and is public
if (initMethod == null || !initMethod.IsStatic || initMethod.ReturnType != typeof(void) || !initMethod.IsPublic)
throw new Exception($"Could not find method {fullName} in assembly: {Location}");
Can you try making AssemblyInfo static and see if that works?
Yes, that’s what I meant. I tried it myself and it seems to work in the KS8400 Editor. Be aware though that depending on what you are trying to do, the init logic may be running before other plugins are initialized.
I’m noticing some superfluous parentheses in the screenshot you provided. Can you try removing them and see if it works?
Can you please confirm me, that I did the integration right? I just added the 2 C# files to the projekt(yellow):
Program.cs
AssemblyLoader.cs
They include the private key and the unlocking algorithm (as in the previous screenshot).
There is at the moment also the attribute included.
Having the init code anywhere in your project should work. The [PluginAssembly] attribute applies to the assembly, and not the namespace / class it is adjacent to, so it doesn’t matter where you define it, and you can only define it once.
There is no log message when the init method is executed, but there is an error message if an init method is specified which could not be executed for some reason. See my previous code snippet which contains a few error cases that could be logged.
You can of course log something yourself in the init method. In order for it to show up in the Editor GUI you should use the OpenTAP logging facilities.
This is my working example:
using OpenTap;
[assembly: PluginAssembly(false, "MyNamespace.InitClass.InitMethod")]
namespace MyNamespace
{
public static class InitClass
{
private static readonly TraceSource log = Log.CreateSource(nameof(InitClass));
public static void InitMethod()
{
log.Info("My module was initialized!");
}
}
}
Maybe you can try copy-pasting this into your project as a sanity check?
I need to say, that it is still not working.
1st I tried to remove the superfluous parentheses. - Did not help.
2nd I tried to implement the log to my code. - Did not help.
3rd I tried to put your code sanity check code, but also this is not working.
→ I was not able to find the Log.Info in the GUI Log.
I attached the log, maybe you can quickly look over it and check if there is maybe anything else not OK.
Is the .NET version (4.7.1) I’m using OK?
Because I found this in the developer guide:
Sorry to hear it’s not working. The log doesn’t really tell me anything.
I tested with .NET 4.7.2, and that works in my case.
It’s difficult for me to know what’s going wrong without looking at what you are doing. One thing to note about the init code is that OpenTAP will only execute it the first time an opentap plugin from your assembly is actually loaded. So for example, if you try to add a Test Step defined in the same dll as your init code, it should be executed the first time you try to create that test step in the Editor.
I created this sample console application using .NET 4.7.2 to verify it works:
using OpenTap;
[assembly: PluginAssembly(false, "MyNamespace.InitClass.InitMethod")]
namespace MyNamespace
{
public static class InitClass
{
private static readonly TraceSource log = Log.CreateSource(nameof(InitClass));
public static void InitMethod()
{
log.Info("My module was initialized!");
}
}
internal class Program
{
public static void Main(string[] args)
{
// Initialize the plugin manager. This is not required when using tap.exe as the entry point.
PluginManager.Initialize();
// Add a console trace listener to ensure log messages using TraceSource appear in standard output.
// This is not required when using tap.exe as the entry point.
Log.AddListener(new ConsoleTraceListener(false, false, false));
// Force loading of all assemblies that define test steps. Note that TypeData.GetDerivedTypes<ITestStep> is
// generally preferred because it supports types not backed by a C# class, and it does not require loading assemblies.
var steps = PluginManager.GetPlugins<ITestStep>();
}
}
// Test step defined in this assembly in order to trigger init code
public class MyTestStep : TestStep
{
public override void Run()
{
}
}
}
now it is working, at least the Assembly artefact “assembly”. I can see the log Info in the GUI.
I just created a new project an implemented this at first, and then it worked. I don’t know why it did not work in previous project.
After that I tried to unlock the sdk with the private key, but it seems to be that the sdk has no Event in the “AppDomain.CurrentDomain.AssemblyResolve”., because the “requestedAssembly.Name” will not be like “Knx.Falcon.ManufacturerSdk”.
static public void Init(string manufacturerPrivateRsaKey)
{
s_manufacturerPrivateRsaKey = manufacturerPrivateRsaKey;
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
}
private static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
{
lock (s_lockObject)
{
AssemblyName requestedAssembly = new AssemblyName(args.Name);
if (string.Compare(**requestedAssembly.Name,** "Knx.Falcon.ManufacturerSdk", StringComparison.InvariantCulture) == 0)
{
// extract public key
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(s_manufacturerPrivateRsaKey);
It will try to resolve just the following assemblys:
I am happy that you got the init function to work at least. One step further
Regarding the Knx.Falcon.ManufacturerSdk not being loaded, it’s impossible for me to tell without more information. Since OpenTAP and dotnet is involved, there are also several confounding factors making it tricky to pin down problems without debugging it myself.
There are significant differences in assembly loading between .NET Framework and .NET Core. From my own observations, it appears that .NET Framework will allow assembly resolve handlers to run before the default framework handler, whereas .NET Core and .NET will only run such handlers if the default handler cannot resolve the assembly. (correct me if I’m wrong)
OpenTAP also uses OnAssemblyResolve. In the scenario where either Knx.Falcon.ManufacturerSdk.dll happens to be loaded before the dll containing your init function, or if the assembly resolve handler from OpenTAP runs before your own handler, your handler will never be called.
For these reasons, it’s hard for me to say what’s going wrong. The OpenTAP session log will contain information about what assemblies it loaded. That should reveal if the assembly was loaded before your handler was created.
OK, thank you for your support.
At least for me it is hard to follow up on this assembly loading topic. I will now contact KNX support, maybe they have another idea.
At least I cound not find any KNX assembly log in the session logs - maybe it is not added to the project?
I did it in that way: in the project browser “rightcklick” to " references-Assemblys" and then browse to the files in the folder, where I stored the dlls.
→ in the project it finds all references - that visual studio has 0 Errors.
Is the way correct, to add a external reference to the OpenTap Package Template?