NI TestStand

cancel
Showing results for 
Search instead for 
Did you mean: 

.NET Unable to find assembly TestStand

Solved!
Go to solution

Hello,

 

I spent half a day on this one... so I'm beat up and fed up...

 

I'm having an issue, I want to create a .NET object using a library I've created.

The assembly(library) has codependencies on several other assemblies. ( all are in the same folder as the sequence file ).

And down the line, when I want to perform the contructor call I get the message

Unable to find assembly X error and I get the stacktrace.

 

Now... I had this thing before when I was using the same .NET lib in a bare VI (bare-meaning not iside a lvproj file), then I read that I need a LabVIEW project in order to properly search for the libraries.

I've done as the help states and all was fixed.

 

So I've created a workspace file, and also I've set the search directory on the directory when I have the assemblies.But it still crashes.

( I'm probably missing something ... 😞 )

 

The only very dirty workaround I've managed to do, is to upload the .NET assemblies that TestStand can't find to the  <TeststandInstallationDir>\bin.

As I figured out that CLR will probably have the SeqEdit.exe set in as one of the possible search locations. And it worked.

 

Please help me out as this isn't the kind of solution I'm going to be able to deploy for a customer.

 

I also cannot add my .NET assemblies to the GAC as they're not strongly signed, and I'm not their developer.

 

Thanks for any help,

Maciej

 

 


0 Kudos
Message 1 of 8
(7,745 Views)

Hello Macie,

 

TestStand uses the Assembly.LoadFrom() method when calling Assemblies from the .NET Adapter. Using this method of calling .NET Assemblies, TestStand will automatically load dependencies from the same directory as the top-level assembly specified in the Module tab of the .NET Adapter Step Settings pane. I've put a simple example together and attached it to this post to demonstrate this behavior.

 

I'm not sure why your assemblies are encountering this behavior and unfortunately it sounds like you don't have the source code for them. You may want to look at the MSDN Help Topic: Locating the Assembly through Codebases or Probing to get an understanding of how the CLR probes for assemblies and see if using a Configuration file would give you the ability to store your assemblies in a directory other than the application directory (the codebase option may be the only one that will possibly work).

 

Hope this helps!

Message Edited by Manooch_H on 10-21-2009 09:47 AM
Manooch H.
National Instruments
Message 2 of 8
(7,717 Views)

Macie -

 

Could you please provide the call stack displayed in the Error dialog? A simple copy and paste to this thread would suffice. This might help me to provide you with a workaround for this problem.

 

Thanks and I look forward to your response!

Manooch H.
National Instruments
Message 3 of 8
(7,712 Views)

Manooch_H, thanks for your interest and prompt reply, I will be looking through your first reply tommorow, as my business day is over.

 

Just to give you some more information, this is the dependencypath :

A.dll

B.dll  

C.exe <--- this is the assembly where TS says it can't find the lib, even though all the dll's are in the same dir.

(4 more dll's, that C.exe depends on)

 

 The requested stacktrace.

 

( I've renamed the assembly names,namespaces, and hashed the version in the stacktrace )

-------------------------------------------------------------------------------

The instance of the .NET class could not be retrieved.

Unable to find assembly 'C, Version=X.X.X.X, Culture=neutral, PublicKeyToken=null'.
Source:  mscorlib   at System.Runtime.Serialization.Formatters.Binary.BinaryAssemblyInfo.GetAssembly()
   at System.Runtime.Serialization.Formatters.Binary.ObjectReader.GetType(BinaryAssemblyInfo assemblyInfo, String name)
   at System.Runtime.Serialization.Formatters.Binary.ObjectMap..ctor(String objectName, String[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, Object[] typeInformationA, Int32[] memberAssemIds, ObjectReader objectReader, Int32 objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable)
   at System.Runtime.Serialization.Formatters.Binary.ObjectMap.Create(String name, String[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, Object[] typeInformationA, Int32[] memberAssemIds, ObjectReader objectReader, Int32 objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable)
   at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryObjectWithMapTyped record)
   at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryHeaderEnum binaryHeaderEnum)
   at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run()
   at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
   at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream)
   at Company.C.Tool.ToolDocument.Open(Stream stream, String defaultToolName)
   at Company.C.ToolSpecification.ToolSpecificationGenerator.LoadToolDocument()
   at Company.C.ToolSpecification.ToolSpecificationGenerator.Generate()
   at Company.B.Tasks.CreateToolSpecification(String toolPath)
   at Company.A.ToolSpecification..ctor(String toolFilePath) in D:\Projects\TestingLibraries\A\ToolSpecification.cs:line 60

 

 


0 Kudos
Message 4 of 8
(7,701 Views)

Hi,

 

I've done some reading on the Assembly.LoadFrom().

Although the mehanism of that function isn't clear to me in 100%... based on what I managed to find out, everything should work the way it is set up now,

but it doesn't.


I still haven't figured this one out.

 

Regards,

Maciej


0 Kudos
Message 5 of 8
(7,679 Views)

Macie,

Thanks for providing the error call stack. This helped us understand the problem such that we could investigate it further and provide a solution for you. Here's the reason you're receiving this error.

TestStand's .NET Adapter uses the Assembly.LoadFrom() method to load the specified .NET assembly into memory. The LoadFrom() method provides the useful feature of searching for any dependencies of the specified assembly within the same directory. This allows TestStand to load assemblies and their dependencies from locations other than the Global Assembly Cache (GAC) or the Application Directory (<TestStand>\Bin). When TestStand calls the LoadFrom() method on the specified assembly and it's dependencies, the assembly and dependencies are loaded into memory in the LoadFrom Context.

When you attempt to deserialize types in your code, the CLR will look to the Load Context for the assembly that contains the type that is deserialized. Because the Load Context is a different context than the LoadFrom Context, the CLR does not recognize that the assembly is already loaded into memory and it tries to load it using the Assembly.Load() method. The Load() method only searches for assemblies in the GAC and the Application Directory. Thus, calling the Load() method on the assembly that contains the type that is deserialized will fail if your assemblies are not located in the GAC or Application Directory and you will receive the error that you're seeing.

There are a couple of solutions to this behavior:

1. You can create an Application Configuration file that uses the Codebase element to point to the location of the necessary assemblies on disk and place the config file (SeqEdit.exe.config) in the Application Directory (<TestStand>\Bin). This would require that you provide a <codeBase> element with the absolute path of the assembly for every assembly that contains types that are deserialized. This approach has a couple of downsides:

  • It requires that you hard-code the absolute path of the assemblies that contain types that are deserialized, such that you would have to remember to update the config file anytime the path to your assemblies changes.
  • It requires that you place a file (the config file) in the <TestStand>\Bin directory and more importantly the Program Files directory (a protected directory).

   
The SeqEdit.exe.config file would look similar to the image below:

 

forum.png

 

2. You can create your own .NET assembly to provide an AssemblyResolve event handler that returns the assembly already loaded in the LoadFrom Context when deserialize is called. This assembly would have to be called at the beginning of your sequence for each of your assemblies that contain types that are deserialized. This is the approach that I would recommend.

I have attached a zip file below that contains an example of the above mentioned problem and proposed solutions. This zip file contains four files:

  • The "Forum File Not Found Test" folder contains the TopLevelAssem solution (written in Visual Studio 2005) which is an example that reproduces the behavior you're seeing. The TopLevelAssem solution builds two assemblies, TopLevelAssem.dll and ReferencedAssem.dll (the assembly that contains a type that is deserialized). TopLevelAssem.dll links to ReferencedAssem.dll.
  • The "Forum File Not Found Solution" folder contains the ResolveAssembly solution (also written in Visual Studio 2005) which is an example of one way to provide an AssemblyResolve event handler in the manner specified in solution 2 above. I wanted to note that similar code could also be implemented in your original assemblies but since you don't have the source code for them, I wanted to provide an example that would be very similar to your situation.
  • The "test sequence_35.seq" provides an example of how you would implement solution 2 using ResolveAssembly.dll. If you were to skip the ResolveAssembly step, you would see the same error you're receiving when you execute the MainSequence. I have saved this sequence file in TestStand 3.5 since I do not know what version of TestStand you are using.
  • The SeqEdit.exe.config file is an example Application Configuration file that displays the use of the <codeBase> element. This is solution 1. You can place this file in the <TestStand>\Bin directory, then update the assembly path to what it is on your computer. Once you've completed this, you can skip the ResolveAssembly step of "test sequence.seq" and you'll notice that you do not receive the error.


Please let me know if you have any questions regarding this example. Hope this helps!

Message Edited by Manooch_H on 10-22-2009 09:40 AM
Message Edited by Manooch_H on 10-22-2009 09:40 AM
Message Edited by Support on 10-22-2009 12:23 PM
Manooch H.
National Instruments
Message 6 of 8
(7,668 Views)
Solution
Accepted by Mac671

Macie -

 

I've updated the ResolveAssembly.dll source code to make it threadsafe. Please use the example attached to this post.

Manooch H.
National Instruments
Message 7 of 8
(7,651 Views)

Works like a charm:)

 

Thank you isn't enough. 😄

But Thank you very much for digging into this, and not only giving a reply but even giving the 🙂 code & dll to fix this.

 

Best Salutaions,

Maciej


0 Kudos
Message 8 of 8
(7,642 Views)