From Friday, April 19th (11:00 PM CDT) through Saturday, April 20th (2:00 PM CDT), 2024, ni.com will undergo system upgrades that may result in temporary service interruption.

We appreciate your patience as we improve our online experience.

NI TestStand

cancel
Showing results for 
Search instead for 
Did you mean: 

Casting an ‘Object Reference’ back to a C# class

Hi,

 

A C# class is constructed during execution of SequenceFileLoad callback, the reference is stored into a StationGlobals Object Reference variable (“Class1”).

In another C# application, the StationGlobal variable containing the object reference is retrieved and I want to cast this reference back to
current instance of my first class to be able to call public methods from my first class.

Below a code snippet and the error that I get, does anyone know how to do this?

 

NationalInstruments.TestStand.Interop.API.PropertyObject propertyObject = axApplicationMgr.GetEngine().Globals.GetPropertyObject("Class1", 0);
NationalInstruments.TestStand.Interop.API.PropertyValueTypes propertyValueType = propertyObject.GetType("", 0, out blnIsObject, out blnIsArray, out strTypeNameParam);
   if (propertyValueType == NationalInstruments.TestStand.Interop.API.PropertyValueTypes.PropValType_Reference)
   {
       object objectReferenceClass1 = propertyObject.GetValInterface(string.Empty, 0);
       Class1 class1 = (Class1)objectReferenceClass1;
       // ************************************************ ERROR ***************************************************************
       // Unable to cast COM object of type 'System.__ComObject' to class type Class1'.
       // Instances of types that represent COM components cannot be cast to types that do not represent COM components;
       // however they can be cast to interfaces as long as the underlying COM component supports QueryInterface calls for the IID of the interface.
       // ************************************************ ERROR ***************************************************************
    }

 

Best regards

0 Kudos
Message 1 of 9
(5,625 Views)

Several issues:

 

1) How did you store the object in the global? Using the Adapter to construct it or creating it within a code module and using SetValInterface? If you used the .NET adapter than you must use the .NET adapter to retrieve it (i.e. use the .NET adapter to pass it into a code module as a parameter). If you used SetValInterface then you must use GetValInterface. You cannot currently mix these kinds of reference storage.

 

2) What is your "other applicaiton" (it must at least be in the same process)? Are you trying to pass a .NET reference from your execution to your user interface? If so, you need to be aware that they are in separate AppDomains. The .NET adapter creates a separate appdomain for executions so that assemblies can be unloaded. In order to be able to successfully pass a .NET object between appdomains, your object needs to be derived from MarshalByRefObject or be serializable. If it's serializable then a copy is made in the other appdomain, if it's MarshalByRefObject then the other appdomain will get a proxy that allows for calling back to the original object in the original appdomain. If you use MarshalByRefObject, you might also need to set the lease time on your object by overriding InitializeLifetimeService() as follows so that it doesn't timeout for cross-appdomain access:

 

        public override object InitializeLifetimeService()
        {
            // MSDN documentation for ILease:
            // "If the InitialLeaseTime property is set to TimeSpan.Zero, then the lease will never time out
            // and the object associated with it will have an infinite lifetime."
            System.Runtime.Remoting.Lifetime.ILease lease = base.InitializeLifetimeService() as System.Runtime.Remoting.Lifetime.ILease;
            if (lease.CurrentState == System.Runtime.Remoting.Lifetime.LeaseState.Initial)
            {
                lease.InitialLeaseTime = TimeSpan.Zero;
            }

            return lease;
        }
 

 

3) If you store the interface using SetValInterface, you must make sure your module load times are such that they aren't unloaded until you are done with the reference because if they are all unloaded then TestStand will unload the .NET adapter's appdomain since it doesn't currently know about .NET objects you've stored using SetValInterface.

 

Hope this helps,

-Doug

0 Kudos
Message 2 of 9
(5,617 Views)

A little bit more details about my problem

 

Attached a drawing …

 

I am using the Editor Interface TestExec application with a customized process model.

This model contains a C# action in the SequenceFileLoad callback. The action uses the  .NET adapter to construct an instance of the TmgSystems.Dll, this reference is stored into the StationGlobals.TmgSystems object reference.

My Editor User Interface has a toolbar, when clicked on a button, the method TmgSystem should be called from my assembly TmgSystems.dll. But in the TestExec user interface I do not have a reference to the TmgSystems instance. So I am trying to retrieve this reference from the station global variable and then cast it back to an object of type TmgSystems.

The TestExec and TmgSystems are running in the same process but in another thread.

 Best regards
0 Kudos
Message 3 of 9
(5,608 Views)

Why are you creating the object in your model rather than in your user interface? Does the model make calls on it as well?

 

-Doug

0 Kudos
Message 4 of 9
(5,604 Views)

Yes indeed, my model make also calls to the object and another reason I want to use the same object also from the sequence editor (not a user interface)

 

This time with the image attached...

 

Best regards

0 Kudos
Message 5 of 9
(5,600 Views)

What does your code do with the sequence context and what are you going to pass for the sequence context from your UI?

 

-Doug

0 Kudos
Message 6 of 9
(5,585 Views)

We have a lot of software components developed that we want to reuse with TestStand.
The TmgSystems.dll contains more public methods, only the constructor and one method is signed in the picture to keep things simple. Via the TmgSystems class, we want to communicate with our existing software components. Each component has a graphical user interface and a lot of parameters can be set by means of TestStand variables. To select a Teststand variable in a GUI the following method is used;

 

TestStand.SequenceContext.Engine.DisplayBrowseExprDialog("Teststand Variables", TestStand.SequenceContext, strLocalizedExpression, intSelectionStartOut, intSelectionEndOut, strInitialVariableName, false, true, out strExpression, out intSelectionStartOut, out intSelectionEndOut);

 

Some of the software components measure something, this information has to be stored into a TestStand variable and all of them are using status variables. So from our code we need to update the TestStand variables, also here I need the sequence context.

 

If methods are called in TmgSystems from the UI, it would be nice to have the sequence context also, but if this is not possible, we will try to find another solution

Best regards

0 Kudos
Message 7 of 9
(5,579 Views)

You should not be holding onto a sequence context for any longer than the lifetime of the sequence execution for which it corresponds. A sequence context is the state of the execution of the current sequence execution. Are you holding onto the sequence context for longer than the lifetime of a call? Or are you passing it in and only using it for the current call?

 

Also you don't really need a sequence context to display the browse expression dialog. You only need the Engine.

 

For example:

 

object notUsed = System.Type.Missing;

myEngineVar.DisplayBrowseExprDialog("", myEngineVar.NewEditContext(null, System.Type.Missing, out notUsed), strLocalizedExpression, etc..)

 

You should probably be using an Editing context in this case anyway since you are bringing up an edit dialog, rather than one that is for a currently executing sequence. You could potentially use engine.NewEditContext() from your UI if you want to as well.

 

With regards to your original question, please reread my original post as all of that information applies to your use case. If you want to share your .NET object between code modules and your UI you will effectively need to share it across appdomains and you must do the following:

 

1) Set the global variable internally in a your code module using SetValInterface and NOT from an output parameter or return value of the call. GetValInterface will not give you back the .NET object if it is set by the .NET adapter using an output parameter or return value.

2) Your object's class must derive from MarshalByRefObject (and you should override the method to control lease lifetime as a explained in my previous post). See all of the information in my previous post related to this.

 

If you do 1 & 2 above you should be able to cast the object you get from GetValInterface back to the class type. You need to be careful not to do unload all modules though or you need to also store a class object directly from an output parameter using the .NET adapter in order to keep the .NET adapter's appdomain alive because that is where your object lives.

 

Hope this helps,

-Doug

 

 

Message Edited by dug9000 on 04-01-2010 01:17 PM
0 Kudos
Message 8 of 9
(5,575 Views)
Thank you Doug, it works!
0 Kudos
Message 9 of 9
(5,548 Views)