NI TestStand

cancel
Showing results for 
Search instead for 
Did you mean: 

TS 24 and .Net 8.0, big disapointement

Solved!
Go to solution

Hello team,

 

I was eagerly waiting for TS 24 to be albe to use my traceability DLL made in .Net 8.0.

 

Since in the service we have installed TS24, lot (most of) old assembly in all version of .Net have issues.

Another colleague already ask about Framework and GAC.
Now i want to know better about .Net8.

Is TS24 native Net Std 2.0, Net8.0 or still the old and unwanted Framework 4.x ?

My DLL works with some king of plugins, which are also C#/ .Net 8.0 assembly.

We load the plugin with the method "Activator.CreateInstance()".
Code below 

jungledede_0-1729524559352.png

With a ".exe" debug in .Net 8.0, no issue, loading and creation of the constructor of my class of my assembly is going as expected.

 

With TS23 and all my dll in .Net std 2.0, loading and creation of the constructor of my class of my assembly is going as expected.
With TS24 and the DLL in .Net8.0 generate System.InvalidOperationException

I am a bit lost here. i canot use my .Net Std 2.0 build as it got deprecated dependencies from Microsoft, so no way i put it in production for the next 15 years.
I need the full Net 8.0 to works.

So i wonder if it not a GAC related issue.
Do you know how i can check?



We were happy of NI finally being up to date. We are kinda disappointed how it have turned. 
We use the GRPC api of teststand and since no update of the git repos are made, we fear we will find compatibility issue when we'll start checking this part..

0 Kudos
Message 1 of 8
(467 Views)

I haven't gone down this rabbit hole yet, but I'll soon have to...

 

Guess you've seen

 

Oli_Wachno_0-1729574201570.png

 

 

From what I understand, the TestStand development has decided to follow a different .net support strategy than the LabVIEW Team for IMHO the sake of implementation speed.

 

Though I have to admit, that I find it quite confusing and difficult to find intuitive information for non-.net pros.

0 Kudos
Message 2 of 8
(433 Views)

Hi jungledede,

 

TestStand is still in the process of migrating completely to .NET Core. The .NET adapter (which is responsible for loading and executing your code module) is already fully in .NET 8.

The Sequence Editor, built-in step types and tools etc which are also .NET based are not migrated yet and this is being worked on for the next TestStand release. Once done, all these applications will be in .NET 8 as well. Currently they are still in .NET framework 4.8.

 

You should be able to target your code modules to .NET 8 and use them with TestStand 2024 Q4, that is the expected workflow.

 

From the fact that you are able to execute your dll through a .NET 8 executable without issues and that the exception is not a FileNotFound but rather InvalidOperation, i would suspect that the issue might not be related to having dependencies in the GAC.

 

Would you be able to provide a simplified version of your code which shows the error so we can check what's going on. Just based on looking at your code snapshot, i am unsure why an InvalidOperation exception is being thrown.

 

Regards,

Tinu

 

 

0 Kudos
Message 3 of 8
(415 Views)

Hello,

After few holidays and days of trying to figure it out, well, I still dont find any solution. I try a lot of ideas, none are working. 

I'll call official support, but honestly i am a bit lost and out of idea to make it work. For sure i cannot change the architecture of my code for this feature.

 

Again, everything is working fine when the exe app that load my DLL is my debug app in .Net8

When loading from teststand 24 i got the issue.

So, let's see what i have try:

 

With better error management and breakpoint, i am able to see that the issues is that we get a type mismatch, and we cannot cast the "type" to the interface.


plugin = (IpluginFoo)Activator.CreateInstance(type);

 

 

I force to compile in x64 for CPU and target win-x64 instead of default values (msil?), no effects

 

With debugguer i compare the type between my manager and my plugin, we got the same Interface (export to file, then win merg tell me no difference)

 

I found the way to see all dll loaded in memory, i see some differences, like a very important one. When using testsand 24, the C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\netstandard.dll is not loaded 🤣 

I was quite happy to see that, well, when i force all the missing assembly, no difference 😖

difference of assembly between native net8 app ond teststand 24difference of assembly between native net8 app ond teststand 24

Load them that way:

jungledede_1-1731050708322.png

 

I haven't yet look inside the NI dll, maybe they redo some in-house dev of very basic code like the Malloc on CVI...

 

I try to use the option of Activator.CreateInstance, still no effect, still the some invalidcast...

jungledede_3-1731050905325.png

As you can see, the interface name is not just IPlugin as we though with colleagues that this name may be used. So, i rename it to add more words and my company name in it, for sure it is not reuse somewhere else.

 

I finish by making a very strip down and basique c# project, and same issue.

jungledede_4-1731051081833.pngjungledede_5-1731051095672.pngjungledede_6-1731051103696.png

 

I join the archive of the simple project + the seq fil that call it


It is a basic feature of c#, i should not be the only one with this issue.

0 Kudos
Message 4 of 8
(327 Views)

By any chanve will it work with self contained set to true?

 

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>default</LangVersion>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>

</PropertyGroup>



Alternatively before calling this module You can use to check what exactly he is missing:

AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve;

 

In net 4.8 we had issues when teststand tried to find some libraries in GAC insted of dll directory and we solved it by redirecting with this event

0 Kudos
Message 5 of 8
(293 Views)

Hi,

no luck. I try to add the self contained but it change nothing.

 

For the second idea, i am not sure it is a missing assembly.
I don't see which one i am missing, and so i don't know which one to add.

I ask Copilot to help me understand your second idea, it give me this :

using System;
using System.Reflection;

class Program
{
    private static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args)
    {
        string assemblyName = args.Name;
        if (assemblyName == "MyCustomLibrary")
        {
            return Assembly.LoadFrom("path/to/MyCustomLibrary.dll");
        }
        return null;
    }

    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve;
        // Your code that uses the assembly
    }
}

 

The debug windows tell me this : 

'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.Private.CoreLib.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\National Instruments\TestStand 2024\Bin\DotNetAdapterManagedSupport.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.Runtime.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.Threading.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.Runtime.Extensions.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.Runtime.InteropServices.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.Runtime.CompilerServices.VisualC.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.Collections.NonGeneric.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\National Instruments\TestStand 2024\Bin\zNationalInstruments.TestStand.DotNetAdapterGAC.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\National Instruments\TestStand 2024\Bin\DotNetAdapterSupportCore.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.Collections.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.Runtime.Loader.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\National Instruments\TestStand 2024\Bin\DotNetCoreAssemblyReader.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.Reflection.Metadata.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.Collections.Immutable.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.IO.MemoryMappedFiles.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.Threading.ThreadPool.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.Text.Encoding.Extensions.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.Memory.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Users\Jungledede\source\repos\net8basic\Manager.dll'. Symbols loaded.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Users\Jungledede\source\repos\net8basic\plugin1.dll'. Symbols loaded.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Users\Jungledede\source\repos\net8basic\Manager.dll'. Symbols loaded.
'SeqEdit.exe' (CoreCLR: clr_libhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.10\System.Diagnostics.StackTrace.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
The thread '[Thread Destroyed]' (38288) has exited with code 0 (0x0).
The thread '[Thread Destroyed]' (43112) has exited with code 0 (0x0).
'SeqEdit.exe' (CoreCLR: clr_libhost): Unloaded 'C:\Users\Jungledede\source\repos\net8basic\Manager.dll'
'SeqEdit.exe' (CoreCLR: clr_libhost): Unloaded 'C:\Program Files\National Instruments\TestStand 2024\Bin\DotNetCoreAssemblyReader.dll'
The thread '[Thread Destroyed]' (28492) has exited with code 0 (0x0).
The program '[45928] SeqEdit.exe' has exited with code 2147500035 (0x80004003).

Do you see in this list a missing assembly ?

 

I modify my example if more people want to test it.
I add a method to get the string value from the plugin, so no need to breakpoint

0 Kudos
Message 6 of 8
(261 Views)
Solution
Accepted by jungledede

Hi jungledede,

 

I was able to try out your sample code and the issue is with using Assembly.LoadFrom.

 

Like i mentioned earlier, the .NET adapter in TestStand 2024 Q4 has been migrated to .NET Core. This means using new AppDomains to isolate user assemblies that are loaded into TestStand and to also provide the capability to unload them is no longer a option. So we now use AssemblyLoadContexts (ALC) instead which is the recommended alternative for .NET Core.

 

A side effect of this is that the behavior of certain .NET APIs have changed (or they have new constraints in the context of using ALCs). Assembly.LoadFrom() for example loads the specified assembly into the Default ALC instead of the current one. A list of these APIs and their load behavior is present here.

 

In your code, this meant that the Manager.dll was loaded in the custom ALC that TestStand was using and the Plugin.dll was loaded into the Default ALC. Both dlls have a dependency on the IPlugin interface and it does get loaded twice, once in the custom ALC from Manager.dll and a different copy is loaded in the Default ALC for Plugin.dll. This is why the typecast from the type returned by the Plugin to the IPlugin in the manager class fails.

Important note: If the same assembly is loaded into different ALCs, the types in the assembly in the different contexts are considered different types  and can't be directly assigned to one another.

 

Another side note on plugins as detailed here, it will be better if you separate out the IPlugin definition from Manager.dll so as to remove the dependency of the Plugin implementation on Manager.dll.

 

I was able to fix your sample code by changing the Assembly.LoadFrom to AssemblyLoadContext.LoadFromAssemblyPath. The code in the Caller() constructor now looks like this:

 public Caller() 
 {
     var current = Assembly.GetExecutingAssembly();
     var pluginPath = Path.GetDirectoryName(current.Location);
     Assembly assembly = AssemblyLoadContext.GetLoadContext(current).LoadFromAssemblyPath(pluginPath + "\\plugin1.dll");

     Type[] allTypes = assembly.GetTypes();
     foreach (Type type in allTypes)
     {
         // 2024/11/05 RD: typecast instead of using 'as'. Give us better error exceptions.
         if (typeof(IpluginFoo).IsAssignableFrom(type))
         {
             plugin = (IpluginFoo)Activator.CreateInstance(type);
         }
     }        
 }

 

Let me know if this works for you.

 

Regards

Message 7 of 8
(232 Views)

Hello, 

Thanks you. It was indeed the solution.
With the explanation is understand the behavior. It is indeed over my current level. I learn something here.

 

9019b545c923e8fab876a26f65262a01-1528535128.gif


Now i go upgrade all the code we have with this command line.

0 Kudos
Message 8 of 8
(185 Views)