Jump to content

Understanding Preserve Run Time Class in an In-place Element Structure


Recommended Posts

I am trying to understand the requirements for replacing the object that a LVOOP DVR refers to. I've looked at these pages, but I'm still not quite sure I have a full grasp:

http://zone.ni.com/r...lassrefswapped/

http://www.ni.com/wh...er/9386/en#toc3

I have what is essentially a LVOOP hardware abstraction layer: a parent class with dynamic dispatch methods, and a number of child classes that override those methods to provide the specific hardware functionality. In a single-loop application, I can initialize a shift register with a parent class object, and then inject a child class object onto that wire as needed.

I am now working on a multiple-loop application, and need a way to serialize access to the hardware object across the loops. It seems like a DVR is the right tool for this. Here is a watered down version of what I am thinking of doing:

post-28228-0-74704300-1353871817.png

Here "Pack" is the parent class, and "PCPV4" is one of the child classes. In real life that case statement would be a state machine.

Some questions:

1) Is it ok to replace a the object that a parent class DVR refers to with a child class object? In my limited testing, as long as PRTC is there, everything works as expected: I can later dereference that DVR and it will act as if there is a child object on the wire: it will call overriden methods, maintain child class data, and so on. But maybe I am abusing something here.

2) Why is PRTC needed? Even though the referred object gets replaced, the new wire is of the parent class type, so at runtime the object is already guaranteed to be of the parent class type or one of its child classes. PRTC doesn't seem to tell LabVIEW anything it doesn't already know.

3) Eventually I will have multiple objects (not all of the same parent class) that I need to keep track of, so my thought was to make a DVR to a cluster of parent objects. Wrapping everything in a cluster seems to get rid of the PRTC requirement. Why?

4) From what I gather from the two links at the top, there are more sinister problems related to swapping objects in a DVR, but I'm having a hard time understanding a) what those problems are, and b) how PRTC fixes them.

Thanks for any replies.

Link to comment

The PRTC primitive is a confusing beast. To understand what it does, you need to understand the difference between the wire type in your diagram, and the actual value type that's riding on that wire at run-time. You might have a wire type of "Pack", but the actual value riding on the wire at run-time could be a Pack object or any descendant class of Pack, including your PCPV4 class, among others.

2) Why is PRTC needed? Even though the referred object gets replaced, the new wire is of the parent class type, so at runtime the object is already guaranteed to be of the parent class type or one of its child classes. PRTC doesn't seem to tell LabVIEW anything it doesn't already know.

The DVR is used to operate in place, which means you need to ensure that the value on that wire is the exact same type as what was originally there. You might be accessing the DVR as a "Pack" wire, but there could be any child class of Pack actually riding on that wire at run-time-- you just don't know ahead of time. That's why you need to check at run-time what type of class is on the wire. The PRTC does this, whereas the other casting operators do not.

The To More Specific Class simply compares a value to whatever wire type is connected to the middle terminal at compile time and is clueless as to other possible descendant classes that might actually be riding on the wire at run-time.

So having said that...

1) Is it ok to replace a the object that a parent class DVR refers to with a child class object? In my limited testing, as long as PRTC is there, everything works as expected: I can later dereference that DVR and it will act as if there is a child object on the wire: it will call overriden methods, maintain child class data, and so on. But maybe I am abusing something here.

"OK" depends on what exactly you're expecting out of the construct you created. Do you expect to actually mutate the type of object on the wire at run-time? If so then I expect your code isn't quite doing what you hope. The PRTC primitive is there to enforce the value type definitely does not mutate at run-time. If the cast fails, then the run-time class from the middle terminal is used (NOT the wire type, but the actual value type that is riding on the wire). Hence the name of the primitive, it preserves the run-time class. Check the error out terminal if you fail to actually preserve the type.

If you're just looking to put a "new" value on the wire but for whatever reason have knowledge that your types will never be mutating, then yes this is pretty much a textbook example of why the PRTC primitive is needed and it will work. But looking at the case structure in you code implies you're actually trying to perform the former.

I don't have time to get into any more detail, but I've done similar things to what I believe you're after. Basically you have a DVR of type "Pack", and what to be able to change the value in the DVR to mutate to an arbitrary child class of Pack, correct? In this case, destroy the DVR and create a new one. The two values are different types, it makes no sense to operate on the values in-place.

-m

Link to comment

The PRTC primitive is a confusing beast. To understand what it does, you need to understand the difference between the wire type in your diagram, and the actual value type that's riding on that wire at run-time. You might have a wire type of "Pack", but the actual value riding on the wire at run-time could be a Pack object or any descendant class of Pack, including your PCPV4 class, among others.

The DVR is used to operate in place, which means you need to ensure that the value on that wire is the exact same type as what was originally there. You might be accessing the DVR as a "Pack" wire, but there could be any child class of Pack actually riding on that wire at run-time-- you just don't know ahead of time. That's why you need to check at run-time what type of class is on the wire. The PRTC does this, whereas the other casting operators do not.

Right, but PRTC does not throw an error if there is a child object on the wire. It only errors if the runtime class of the "in" object is higher in the class hierarchy than the target. I believe this is by design, otherwise you would break dynamic dispatch for children of the child. This is why I'm confused. If the parent DVR can't accept a child object, then PRTC is not going to stop that from happening.

"OK" depends on what exactly you're expecting out of the construct you created. Do you expect to actually mutate the type of object on the wire at run-time? If so then I expect your code isn't quite doing what you hope. The PRTC primitive is there to enforce the value type definitely does not mutate at run-time. If the cast fails, then the run-time class from the middle terminal is used (NOT the wire type, but the actual value type that is riding on the wire). Hence the name of the primitive, it preserves the run-time class. Check the error out terminal if you fail to actually preserve the type.

As far as I can tell, it does mutate. I can make a child object, modify its data, and stuff it as shown into a parent DVR without any error from PRTC. Later on, if I dereference the DVR, the resulting object is still the same child (even though it's on a parent wire) and will call overriden methods.

I don't have time to get into any more detail, but I've done similar things to what I believe you're after. Basically you have a DVR of type "Pack", and what to be able to change the value in the DVR to mutate to an arbitrary child class of Pack, correct? In this case, destroy the DVR and create a new one. The two values are different types, it makes no sense to operate on the values in-place.

The trouble with this approach is that it will break the DVR in the other parallel loops.

Link to comment

ps, Thanks for your reply. Based on what the LabVIEW DVR documentation says, I agree in principle with what you wrote. In practice it seems to do something else. I'm trying to figure out if this is expected behavior or not.

I don't have a quick example to show a method override, but here's a stupid example that shows that the object mutates and preserves child class data. The DVR is a reference to an LVObject, but I use PRTC to stuff in a "Node List" object that has previously modified class data. Later on I get the object back, downcast it to Node List, and get the data out.

post-28228-0-40710300-1353891345_thumb.p

In a larger application I was able to call an overriden method from an IPE using a similar technique.

  • Like 1
Link to comment

Neat. I don't think I've ever thought to use the PRTC primitive in that context. I've never considered using the IPE structure to change type because the two ideas are incompatible in my mind. But there you have it.

Right, but PRTC does not throw an error if there is a child object on the wire. It only errors if the runtime class of the "in" object is higher in the class hierarchy than the target. I believe this is by design, otherwise you would break dynamic dispatch for children of the child.

Well there are other situations it can throw an error, but the code you showed makes sense. Your Node List is a LabVIEW Object by inheritance, so the run-time type is preserved, even if the actual type is a descendant of the target.

The code above though is deceiving because everything inherits from LabVIEW Object. If your DVR was of a class unrelated to Node List (other than the common LabVIEW Object ancestor) then the cast definitely fails, which would be the expected behavior.

However there you have it, your example. I guess then the DVR can mutate in type, so long as the mutation results in a more specific run-time object? This seems...odd to me. I too can't help but wonder if that's intended behavior. Need some time to mull this one over.

Editing doesn't seem to work right now, so I'll clarify:

Your Node List is a LabVIEW Object by inheritance, so the run-time type is preserved, even if the actual type is a descendant of the target.

The above statement wasn't meant to stand outside the context of my earlier post. The fact is the example you showed indeed works, so I can understand the argument that since any object is a LabVIEW Object, you can also say that indeed yes, there has been some type of preservation in that the resulting value on the DVR is still a LabVIEW Object-- I'm just not really convinced this is supposed to be able to happen. I would rather argue that since the type is in fact mutating that there is no preservation.

But maybe this is all by design?

Link to comment

The code above though is deceiving because everything inherits from LabVIEW Object. If your DVR was of a class unrelated to Node List (other than the common LabVIEW Object ancestor) then the cast definitely fails, which would be the expected behavior.

Agreed. I didn't have a suitable class hierarchy immediately on hand to make a simple example, so I cheated. Trust me that it works the same way with a user-defined parent and child.

However there you have it, your example. I guess then the DVR can mutate in type, so long as the mutation results in a more specific run-time object? This seems...odd to me. I too can't help but wonder if that's intended behavior. Need some time to mull this one over.

There is some strangeness here, especially when you read the LabVIEW documentation about using classes with DVRs and IPEs, and the fact that PRTC is required. Absent that though, this is how I would expect it to work. A child object is technically is of the parent type, and you can mix parent and child objects in arrays, clusters, queues, etc--as long as they are of the parent type.

If you really want to blow your mind, as I alluded to in my question #4 in my first post, you can get rid of the PRTC by wrapping the object in a cluster:

post-28228-0-92166000-1353905410_thumb.p

There's a cryptic sentence in the "using references with classes" link above that says you can do this to swap objects without PRTC, but I'm not sure what's going on under the hood that makes this work, or even if it's supposed to work with child objects.

Edited by nileracecrew
  • Like 1
Link to comment

I still haven't gotten my head around this VI.

From the help file (and Icon) it appears that it is specifically designed for downcasting in the case where LabVIEW may not think it can go as far down the hierarchy as you know it can. I think it is specifically for downcasting as upcasting can be done just by wiring (with a coersion dot) but the examples here show it can be used for upcasting as well.

My question is what exactly is the difference between this and the stock downcast. I believe the downcast will also throw an error if the cast cannot be done. It appears to be designed to be an inline operation, everything suggests there must already be a object.

Link to comment
My question is what exactly is the difference between this and the stock downcast. I believe the downcast will also throw an error if the cast cannot be done.

Illustration:

A Child object on a Parent-type wire being cast into a "target" Grandchild object on a Parent-type wire will produce an error with PRTC (note that the “stock downcast” works on wire types, not the actual objects).

The point of this is that the compiler can be certain that the object coming out of the PRTC is the same as the target object; this means it can trace through the input wire type to the output of the subVI at edit time. So if you write a subVI with Parent inputs and outputs, and connect a grandchild wire to the input, the output wire will take on grandchild type. See below an example subVI I made recently using PRTC; note that I can “pass through” wires of different object types (the actual subVI uses LVObject inputs and outputs).

post-18176-0-10214500-1354029984.png

I should add that the PRTC is only needed if the compiler cannot already tell that the object out is the same as the object in. In my subVI above, I’m receiving an object in a message and using PRTC to “cast” it as an object of a specific type. Inside the subVI, the wires are LVObject, so the stock cast functions do nothing.

It is confusing at first.

Link to comment

I still think this is a "half" solution. With variants, we should just be able to wire an output to an indicator and it should just do it without explicit conversion.

Well, currently they do let wiring to an indicator define the data type. Unfortunately it has to be directly wired, with no other structure in between, which limits its usefulness:

post-18176-0-21219800-1354278270.png

And you still need the “Variant to Type” primitive. But where would type-conversion errors go otherwise?

Also, the first thing I would do, if my idea was implemented, would be to make a “Variant to Numeric with Units” VI that would enforce matching units between the Variant and the supplied type. Your idea wouldn’t help with that.

Link to comment

It also requires that you know the type ahead of time to drop the correct terminal. Which of course is the point of the "run time" preservation in the suggestion (yes I know there's nothing "run time" about it, but we are drawing an analogy).

Link to comment

It also requires that you know the type ahead of time to drop the correct terminal. Which of course is the point of the "run time" preservation in the suggestion (yes I know there's nothing "run time" about it, but we are drawing an analogy).

You also need to know the type ahead of time with the others as well (supply a control to define the type). I would prefer it just coerces to the type of the indicator that I hang off of it which in fact is more useful than the "To Data" and James would get his function without having to define the type input. It (i think) should behave like a polymorphic VI but we don't have to write all the cases.

Until that happens. I'm still using strings and variants (to me) are still the feature that never was..

Can I also reiterate my long standing peeve about not being able to create "native" polymorphic controls/indicators. X controls is another "half" solution. :D

Link to comment

I am now working on a multiple-loop application, and need a way to serialize access to the hardware object across the loops. It seems like a DVR is the right tool for this.

I haven't read through all the replies on this thread, but this comment jumped out at me.

As an alternative, have you considered writing an instrument actor instead of using by-ref objects? Personally, I find actor-oriented programming much better than reference-based programming for multi-threaded applications. YMMV.

Link to comment

I haven't read through all the replies on this thread, but this comment jumped out at me.

As an alternative, have you considered writing an instrument actor instead of using by-ref objects? Personally, I find actor-oriented programming much better than reference-based programming for multi-threaded applications. YMMV.

I am starting to lean that way as well. In this particular case, it might create some headaches because of a need to coordinate and sequence multiple subactors. I am still trying to sort out the best approach. The actor model is new to me, but fortunately with this project I have the luxury of a little time to research and make a few prototypes before settling on an architecture. FYI, your Slave Loop post really helped my brain get around the whys and hows using actors, so big thanks for that.

Link to comment

Back to the original topic, I did find a significant caveat when using the method described above to change the object that a DVR refers to. Suppose I have a DVR that refers to a parent-class object. I replace the object with a child-class object, then later down the wire--works fine. Then, I try to replace it again with a parent-class object. The second replacement will fail since PRTC will (correctly) fail--a child-class object is on the middle wire, but I am trying to pass in a parent-class object.

So, that is the case that LabVIEW seems to be protecting against: parent class DVR refers to a child-class object, and you try to swap in a parent-class object. I still don't understand why that's a problem. I'm guessing it's an implementation issue?

I made a related cross-post on the NI forums to see if anyone there has answers.

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.