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.

LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Type safe dynamic storage of inherited objects?

Solved!
Go to solution

Hello,

 

I'm trying to store mixed type inherited objects of a common base class in a way that preserves the original type.

I tried it with attributes of functional globals, with labview globals, all without success. Whenever I read out a stored object, it degrades to the base class.

Attached is a project that should illustrate the behavior.

 

- base class "Device Base" with member VIs "Init", "Read", "Write" (intended to act as interface)

- derived classes "GPIB", "TCP", "DAQmx"

 

At startup all required deviced (depends on test script) are created and stored in the device manager. The device manager is a functional global, containing a variant that manages a lookup list of "DeviceId=>'Device Base' Object" assignments.

 

Storing and reading to and from manager works, at least until the VI borders are reached. Within the VI, the correct original type (e.g. "Device GPIB") is returned. Outside of the VI, the type is casted to "Device Base" and the wrong dynamic dispatched member VI gets called (Base implementation)

 

How can I safely dynamically store and read mixed objects of the same base class without loosing the concrete type?!

0 Kudos
Message 1 of 19
(3,162 Views)

You need to do two things.

 

One, program your classes so that they can identify themselves (either via hard-coded strings or some dynamic data or even built-in LV primitives).  This way you can take a base class and pass it to a dummy object of the required class and find out if it's the same type.

 

Then you write a routine to retrieve the object from your base class list but feed it into a "Preserve run time class" on the output.  This will then allow you to wire in a particular class type as an input and get that correct type presented (strictly typed) at the output.

 

Shane.

Message 2 of 19
(3,147 Views)

Hi,

 

as this is the english forum i will answer in english. I don't translate the original question as i hope that my answer is quite self-explaining esp. when looking into your attached code.

 

Short answer:

It does not work because you work against the paradigms of LV OOP.

 

Long answer:

Your initial test with casting the object your Device Manager returns when calling "get" fails with error 1448: Bad type cast.

The reason for this is that the object is of type "Device Base" already and lost all information the specific child class added to. So ignoring the error works for your current use case (as you only use dynamic dispatch), but it will fail as soon as the child defines custom properties which will be used in the override functions.

 

The question you could now ask is: Why has the object lost the information "Device specific" and is now "Device Base"?

This fact is created by your Device Manager itself. To be more specific: The return parameter "Device object out". This is an indicator of type "Device Base", so anything compatible will be cast to this base class. Hence, from now on, you can only call Device Base specific functions and have only the Device Base incorporated properties. Anything else is gone.

 

The only option to prevent a real upcast in the inheritance hierarchy would be a dynamic dispatching in- and outputs. This is only possible if the Device Manager itself is part of the Device Base class.

Dynamic dispatch inputs are required, so your reader (get function) had to connect a class constant of the correct class as input.

 

See attached code for a possible solution. Please note that the changes i have done to your Device Manager should not be necessary except the change of moving it to the Device Base class and configuring the class in-/output as "dynamic dispatch".

 

hope this helps,
Norbert

 

PS: Code attached is 2012 now

Norbert
----------------------------------------------------------------------------------------------------
CEO: What exactly is stopping us from doing this?
Expert: Geometry
Marketing Manager: Just ignore it.
Message 3 of 19
(3,144 Views)

Shane,

 

hehe, nice alternative solution with "Preserve Run-Time Class".

 

Advantage of your solution:

Device Manager is still "stand-alone" outside the classes (pure usage).

Disadvantage:

If the class does not identify itself gracefully, it will not change from status quo the OP is seeing.

EDIT: Also, it is more effort if you create additional child classes as you have to extend the case structure working on the preservation of the child class type.

 

Norbert

Norbert
----------------------------------------------------------------------------------------------------
CEO: What exactly is stopping us from doing this?
Expert: Geometry
Marketing Manager: Just ignore it.
Message 4 of 19
(3,142 Views)
Hello Norbert, hello Shane,
thanks for your fast replies!
I'll check your proposals on monday and keep you informed.
Best regards an have a nice weekend,
Alex
0 Kudos
Message 5 of 19
(3,127 Views)

@Norbert_B wrote:

Shane,

 

hehe, nice alternative solution with "Preserve Run-Time Class".

 

Advantage of your solution:

Device Manager is still "stand-alone" outside the classes (pure usage).

Disadvantage:

If the class does not identify itself gracefully, it will not change from status quo the OP is seeing.

EDIT: Also, it is more effort if you create additional child classes as you have to extend the case structure working on the preservation of the child class type.

 

Norbert


Oh lord no.  That's free.  If you wire an object type in on a non-dynamic dispatch input and connect this directly to the type of the "Preserve run time class" LabVIEW automagically updates the output type for you.  There IS NO CASE STRUCTURE.  That's the beauty of the solution.

 

Autocast.png

 

Asusming all the classes have a UNIQUE identifier (This could also be an internal data field by the way) then there is no case structure.  The method to retrieve the unique ID is a must override of the parent.

 

This is how I do it at least.  When you do this, the input and output of this VI shown is the same type.

 

Warning: This image may confuse more than it helps.....

 

Autocast 2.png

Message 6 of 19
(3,124 Views)

Ah, OK, i see what you refer to.

Preserve Run-Time Class.PNG

 

You require the target object to make this work. I thought that you would use class constants for this, so the Preserve Run-Time Class function had to be in a case structure, each case having a different class constant.

 

If using the class input of the subvi as target class, the solution does not need a case structure, but require the caller to pass a default object just as my code does.

 

Norbert

Norbert
----------------------------------------------------------------------------------------------------
CEO: What exactly is stopping us from doing this?
Expert: Geometry
Marketing Manager: Just ignore it.
0 Kudos
Message 7 of 19
(3,117 Views)

In my example I was essentially working with lots of singletons, so it was a bit simpler.  I only have ONE instance of each class.

 

Having multiple instances requires better identification (Mine were hard-wired).  You can, however, design a polymorphic VI with a nice drop-down menu to select which type of object you want to retrieve, making the interface a bit nicer.

 

This is one feature I find excellent in newer versions of LabVIEW.

 

Shane.

0 Kudos
Message 8 of 19
(3,115 Views)

Hello Norbert and Shane,

 

after reviewing your explanations I now understood the problem. The common information in your solutions is that I have to pass the target class to the input.

=> I could get the example to work with a single modification: when passing the correct output class to the device manager input ('get' action), it outputs the correct object class.

modified example.png

 

To my surprise I didn't have to modify my device manager, even no "preserve runtime class" was required. As long as the correct object type is provided at the input, the output is automatically casted to the same type.

 

However, this is not really applicable in my real use case (the example was just to demonstrate the problematic and might have led you on the wrong path).


What I really tried was to create an interface-like behavior where I override an abstract method and just call the interface function without (!) knowledge of the concrete class type. The use case is an implementation of a TestStand based script interpreter where devices are dynamically registered at the beginning of the test. Succeeding steps just provide the deice id (a string) and some output statements. Depending on the device class, the appropriate output function should be called.

 

To make it short:

Your information really helped me out, as I can get the correct objects by making a double lookup (get the base object type, extract the class type from the base properties, pass it to a case structure with class constants, then repeat the lookup).

But finally Norbert is right:

 

Short answer:

It does not work because you work against the paradigms of LV OOP.

 

So this is a workaround that I don't really like, because of the additional lookups and the additional case structure in every wrapper VI. This makes it more difficult to extend the solution with more classes. I must confess, this is my second project using LVOOP, so I might have been a bit naive expecting such common OOP features like just calling an interface function without loosing object information. Let's call it paradigm, but in my LVOOP beginners eyes it's just an incomplete OOP implementation...

 

This is how the real use case (TestStand wrapper for an output function) now looks like:

modified wrapper.png

However, it works and you saved my day 😉

 

Thanks and best regards,

Alex

 

0 Kudos
Message 9 of 19
(3,077 Views)

I was too optimistic...

 

After adding additional specific member data for the classes, this approach does no longer work (just as Norbert predicted). All specific data is lost when reading back the objects. The type is OK, but it uses the default data from the constants provided at the input.

 

@Norbert: your solution suggested that it would work when moving the device manager to the base class and change the terminals to dynamic dispatch, but I had no success.

I attached your modified solution to illustrate the problem. I extended the GPIB class to store the VISA ressource name and to use it as infotext string in the Init.vi override. This information is lost after re-casting the object, even when using "preserve runtime class".

 

Did I made a mistake or is there simply no solution?

 

Alex

0 Kudos
Message 10 of 19
(3,044 Views)