Jump to content

Call parent method node


Recommended Posts

I thought I understood how dynamic despatch worked and calling parent methods within a dynamic dispatch worked, but now I'm having second thoughts :o ...

Suppose I have a class hierarchy like this:

A.lvclass |----B.lvclass |---C.lvclass        |--D.lvclass        |--E.lvclass

Now B.lvclass has private data that is of type A.lvclass. And all classes have a dynamic-despatch method M

Now in a particular wire of B.lvclass, the private data contains an instance of D.lvclass

B.lvclass:M invokes Call Parent Method on it's private data (which is an instance of D, but with control type A). What version of M is run ?

Is it A.lvclass:M since that is parent method of B.lclass:M (which is what I would expect), or is it C.lvclass:M which is the parent method D.lvclass:M

ie. does Call by Parent Method work out which class to execute by looking at the type on wire on its dynamic despatch terminals or by looking at the class of the calling vi ?

Edited by gb119
Link to comment

Wow. I think you've created a situation I never expected anyone to construct. The answer to your question is easy, but you make me think we ought to kill the functionality you're taking advantage of. And, more than what we (LV R&D) ought to do about it, I really want to know why the heck you are in this situation!

Short answer: The Call Parent Method always calls the ancestor implementation of the VI. It does not care about the type of the object on the wire. So Call Parent Method on the block diagram of B:M.vi will always call A:M.vi.

Long answer: Somehow you have an instance of D on a diagram of B:M.vi and you are passing that D object to the Call Parent Method node. How did you get this instance of D on the diagram of B:M.vi? Here is how I would have expected that it got there:

  1. Top level VI is "Caller.vi"
  2. Caller.vi has an instance of D on its diagram
  3. Caller.vi makes a dynamic dispatch call to M.vi, which dispatches to D:M.vi.
  4. D:M.vi uses the Call Parent Method to invoke C:M.vi.
  5. C:M.vi uses the Call Parent Method to invoke B:M.vi.
  6. And now you are asking what the behavior will be when B:M.vi uses Call Parent Method, and the answer is A:M.vi.

Now, that's what I would have expected. Your question implies that something like this happened:

  1. Top level VI is "Caller.vi"
  2. Caller.vi has an instance of B on its diagram.
  3. Caller.vi makes a dynamic dispatch call to M.vi, which dispatches to B:M.vi.
  4. On the diagram of B:M.vi, you somehow obtain an object of type D, either from a diagram constant directly on the diagram or retrieving this D object from some data store (global variable, queue, etc, or maybe just by unbundling data stored in the B object).
  5. And now you propose to wire that D object to the Call Parent Method.

Please tell me that is NOT what you are doing. It sounds like it is, but it shouldn't be happening. There should never be a reason why you would ever want to do this. If you directly pass that D to the Call Parent Method, you are destroying the data integrity of your D object. Any object assumes that if it provides an override of any method then the ancestor method will never act on that object unless it was because the overriding VI used the Call Parent Method to invoke it.

I and my team never discussed this case. The fix for the LV compiler is obvious and easy -- the dynamic dispatch input terminal of the Call Parent Method should only accept wires that propagate down from the dynamic dispatch input of the invoking VI. In other words, only the wires that have the gray background should be able to wire to the dynamic dispatch input of the Call Parent Method, and the VI should be broken if anything else is wired. That is not enforced today, mostly because it never occurred to anyone that you might ever try something like this.

I could be completely off base in my understanding of what you're asking. But if I've hit the nail on the head, can you please explain *why* you are passing this D object to the Call Parent Method without climbing up through the hierarchy, starting at D:M.vi? If there's a real-world use case for this, I am going to be absolutely stunned. My bet is that you're just creating a situation in which D is not being updated correctly.

Now, it is legit to have this situation (steps 1 to 4 are identical to the previous steps):

  1. Top level VI is "Caller.vi"
  2. Caller.vi has an instance of B on its diagram.
  3. Caller.vi makes a dynamic dispatch call to M.vi, which dispatches to B:M.vi.
  4. On the diagram of B:M.vi, you somehow obtain an object of type D, either from a diagram constant directly on the diagram or retrieving this D object from some data store (global variable, queue, etc, or maybe just by unbundling data stored in the B object).
  5. You make a new dynamic dispatch call to M.vi, passing the D object as the dynamic dispatch input. This will call out to D:M.vi and may eventually make a recursive call into B:M.vi, if all of the override VIs keep invoking the Call Parent Method. This eventually results in a recursive call to B:M.vi, which will either panic stop if the VI is not reentrant or will allow the recursion if the VI is reentrant. You'd need in that case to make sure that B:M.vi is written such that infinite recursion does not occur.

Does this sound more like what you're intending to write?

  • Like 1
Link to comment

Wow. I think you've created a situation I never expected anyone to construct. The answer to your question is easy, but you make me think we ought to kill the functionality you're taking advantage of. And, more than what we (LV R&D) ought to do about it, I really want to know why the heck you are in this situation!

Ok, just so that you get some idea of how the thought processes of your users go....

My program is measuring a widget as a function of temperature, magnetic field, voltage applied to device etc. All of these measurements essentially follow a pattern of construct a list of temperature or whatever, iterate through them one by one, measure widget, save data repeat until done.

So I write a base class (A.lvclass) that knows about constructing a list of values, initialising somehing, iterating to the next value, working out when all the measurements are done and a whole bund of other things. I then implement each type of measurement (magnetic field, temperature, applied voltage whatever) as a subclass of A - these are classes C,D,E etc. All is fine in the world and my students give me data.

Then my students come and say "ok, this is fine, but I want to measure over both field and temperature". So I think, I could just expand my program to work with 2 instances of class A separately, but somebody is bound to ask to iterate over 3 parameters, so instead the cunning way to do this to make a new descendent class of A (B.lvclass) that takes an array of child classes of A and for each method simply calls the corresponding method for each element in my array of class A. Since dynamic despatch works, I'll call the correct method for whatever class is actually stored in my array. So I'm doing the second version of your list of operations below.

Now I get lazy - I'm essentially writing the same block diagram for each and every method class B - viz, get array of class A, index through it, calling methods, stuffing the elements of the array of class A back into the array and thence back into class B's private data. So being lazy I wonder if I can just keep doing Save As for each method having used the call parent method node so that it automatically picks the correct method.

Then I realise that I'm not a 100% sure what call parent node does in this circumstance. :frusty:

Short answer: The Call Parent Method always calls the ancestor implementation of the VI. It does not care about the type of the object on the wire. So Call Parent Method on the block diagram of B:M.vi will always call A:M.vi.

Long answer: Somehow you have an instance of D on a diagram of B:M.vi and you are passing that D object to the Call Parent Method node. How did you get this instance of D on the diagram of B:M.vi? Here is how I would have expected that it got there:

  1. Top level VI is "Caller.vi"
  2. Caller.vi has an instance of D on its diagram
  3. Caller.vi makes a dynamic dispatch call to M.vi, which dispatches to D:M.vi.
  4. D:M.vi uses the Call Parent Method to invoke C:M.vi.
  5. C:M.vi uses the Call Parent Method to invoke B:M.vi.
  6. And now you are asking what the behavior will be when B:M.vi uses Call Parent Method, and the answer is A:M.vi.

Now, that's what I would have expected. Your question implies that something like this happened:

  1. Top level VI is "Caller.vi"
  2. Caller.vi has an instance of B on its diagram.
  3. Caller.vi makes a dynamic dispatch call to M.vi, which dispatches to B:M.vi.
  4. On the diagram of B:M.vi, you somehow obtain an object of type D, either from a diagram constant directly on the diagram or retrieving this D object from some data store (global variable, queue, etc, or maybe just by unbundling data stored in the B object).
  5. And now you propose to wire that D object to the Call Parent Method.

Please tell me that is NOT what you are doing. It sounds like it is, but it shouldn't be happening. There should never be a reason why you would ever want to do this. If you directly pass that D to the Call Parent Method, you are destroying the data integrity of your D object. Any object assumes that if it provides an override of any method then the ancestor method will never act on that object unless it was because the overriding VI used the Call Parent Method to invoke it.

I and my team never discussed this case. The fix for the LV compiler is obvious and easy -- the dynamic dispatch input terminal of the Call Parent Method should only accept wires that propagate down from the dynamic dispatch input of the invoking VI. In other words, only the wires that have the gray background should be able to wire to the dynamic dispatch input of the Call Parent Method, and the VI should be broken if anything else is wired. That is not enforced today, mostly because it never occurred to anyone that you might ever try something like this.

I could be completely off base in my understanding of what you're asking. But if I've hit the nail on the head, can you please explain *why* you are passing this D object to the Call Parent Method without climbing up through the hierarchy, starting at D:M.vi? If there's a real-world use case for this, I am going to be absolutely stunned. My bet is that you're just creating a situation in which D is not being updated correctly.

Now, it is legit to have this situation (steps 1 to 4 are identical to the previous steps):

  1. Top level VI is "Caller.vi"
  2. Caller.vi has an instance of B on its diagram.
  3. Caller.vi makes a dynamic dispatch call to M.vi, which dispatches to B:M.vi.
  4. On the diagram of B:M.vi, you somehow obtain an object of type D, either from a diagram constant directly on the diagram or retrieving this D object from some data store (global variable, queue, etc, or maybe just by unbundling data stored in the B object).
  5. You make a new dynamic dispatch call to M.vi, passing the D object as the dynamic dispatch input. This will call out to D:M.vi and may eventually make a recursive call into B:M.vi, if all of the override VIs keep invoking the Call Parent Method. This eventually results in a recursive call to B:M.vi, which will either panic stop if the VI is not reentrant or will allow the recursion if the VI is reentrant. You'd need in that case to make sure that B:M.vi is written such that infinite recursion does not occur.

Does this sound more like what you're intending to write?

Yes - basically I was just being lazy ! Still, at least I've learnt something interesting even if it does mean I have to go back through my class replacing all of the call parent nodes with the correct specific method. Although your comments about reentrancy do remind me I ought to put a test to make sure than I'm never actually have an element of class B or a descendent of it in my array of class A.

Link to comment

Please tell me that is NOT what you are doing. It sounds like it is, but it shouldn't be happening. There should never be a reason why you would ever want to do this. If you directly pass that D to the Call Parent Method, you are destroying the data integrity of your D object.

Can you explain what you mean by 'destroying the data integrity of your D object?' I wired up a simple project to try and follow through the different scenarios you described and I didn't see data internal to D get corrupted or reset.

Class Spaghetti.zip

Then my students come and say "ok, this is fine, but I want to measure over both field and temperature". So I think, I could just expand my program to work with 2 instances of class A separately, but somebody is bound to ask to iterate over 3 parameters, so instead the cunning way to do this to make a new descendent class of A (B.lvclass) that takes an array of child classes of A and for each method simply calls the corresponding method for each element in my array of class A. Since dynamic despatch works, I'll call the correct method for whatever class is actually stored in my array. So I'm doing the second version of your list of operations below.

I'm far from an OOP expert so I may be way off base here, but that inheritance structure strikes me as odd. Class A is your GenericMeasurementObject and Class B is an array of GMOs. Doesn't that violate the "Class B is a Class A" rule of thumb for inheritance? What you're describing might work but I'm concerned your path will lead you off into the weeds (having spent much time there myself) with an application that is difficult to maintain or expand.

Link to comment

I'm far from an OOP expert so I may be way off base here, but that inheritance structure strikes me as odd. Class A is your GenericMeasurementObject and Class B is an array of GMOs. Doesn't that violate the "Class B is a Class A" rule of thumb for inheritance? What you're describing might work but I'm concerned your path will lead you off into the weeds (having spent much time there myself) with an application that is difficult to maintain or expand.

No, that bit's ok - class B isn't an array of GMO's - it has private data that is an array of GMO's. Aristo Queue demonstrated this sort of an idea in the Map LVOOP example that will be buried somewhere in hte LAVA 1.0 content.

The problem, I realised as I walked hom through the rain tonight is that (apart from living in a wet climate !) I was being terminally confused over what methods would execute when the call parent node ran - even if it had worked on the basis of the dynamic despatch class inputs rather than the owning VI's class, it wouldn't have done the correct thing for me. Also consider what would have happened if I really did have an instance of class A going into it - what method would have run ? That's not saying that the original question was stupid - it's not obvious how call parent method node determines what the parent is, and it would probably be a good idea if the compiler required the dynamic despatch marker in the class wire to enusre people like me don't write crazy code.

Link to comment
Can you explain what you mean by 'destroying the data integrity of your D object?' I wired up a simple project to try and follow through the different scenarios you described and I didn't see data internal to D get corrupted or reset.
You didn't see the data for D change... and *that's* the problem. ;-)

Let's take a simple case:

Class A has a private field "numeric" (default value 0)Class B inherits from A and has a private field "numeric2". (default value 0)Class C inherits from B and has a private field "numeric3". (default value 0)Class A implements a static dispatch VI "Get Numeric.vi" that returns the value of its private numeric field.Class A implements a dynamic dispatch VI "Increment.vi" that unbundles Numeric, adds 1, and bundles it back into the object.Class B overrides "Increment.vi" to unbundle Numeric2, add 1, and bundle it back into the object. Then it invokes the Call Parent Method.Class C overrides "Increment.vi" to unbundle Numeric3, add 1, and bundle it back into the object. It DOES NOT invoke Call Parent Method.

What happens when we call "Increment.vi" and then call "Get Numeric.vi"?

If the object is of type A, we dispatch to A:Increment.vi, and Get Numeric.vi returns 1.If the object is of type B, we dispatch to B:Increment.vi, call up to A:Increment.vi and Get Numeric.vi returns 1.If the object is of type C, we dispatch to C:Increment.vi, and Get Numeric.vi returns 0.

Would you agree with me that any time I call "Get Numeric.vi" on a C object that I should get zero as the answer? Since C has overridden Increment, there's no other way for that field to be modified. So one of the invariants of C objects is that Get Numeric always returns zero.

Now, what happens if, on the diagram of B:Increment.vi, we wire a C object directly to the Call Parent Method node? When we call "Get Numeric.vi" on that C object, we get the answer "1". That violates one of the invariants of C objects. The defined interface of the class is broken. And that's a problem.

  • Like 2
Link to comment

Now, what happens if, on the diagram of B:Increment.vi, we wire a C object directly to the Call Parent Method node? When we call "Get Numeric.vi" on that C object, we get the answer "1". That violates one of the invariants of C objects. The defined interface of the class is broken. And that's a problem.

Ooh, what have I started here !?

So basically it's a way of forcing the base method to be invoked even if the immediate parent classes have over-ridden it.I guess this becomes a way of achieving partial inheritance - given my original inheritance tree, a class D can fall back to some methods from class C in the normal way, but if there are some cases where class D wants to make sure class A's method is run, then it implements the methof itself and uses a helper class B which uses this trick to call class A method directly on the class D instances.

Of course it would probably have been easier just to reimplement the original behaviour in class D - except that this way the A class private data in the instance of class D can be used directly and the programmer of class D doesn't need to know the details of the class A implementation to access it. On the third hand, it makes my head hurt :unsure::unsure:

  • Like 1
Link to comment

Now, what happens if, on the diagram of B:Increment.vi, we wire a C object directly to the Call Parent Method node? When we call "Get Numeric.vi" on that C object, we get the answer "1". That violates one of the invariants of C objects. The defined interface of the class is broken. And that's a problem.

Thanks for the clarification AQ. I admit that once I understood what you were saying (it only took a dozen reads) my immediate question was "so what?" I didn't see how it would have much impact in real world applications. I mean, sure it violates the absolute principle of encapsulation and it could cause some odd bugs in obscure corner cases, but is it anything to get alarmed about? Probably not, because in your example Class B (the parent) code was modified to violate Class C's (the child) defined interface. It appeared to require injecting a rogue class into the inheritance tree.

Then I read gb's comments. Now... it may take me a bit longer (okay... a lot longer) to get all the way around the block, but eventually I'll get there.

I started wondering if this could be used to exploit released code. I rigged up a simple class hierarchy with a Textbox object and Password object. This is certainly not a good example of secure coding practices, but it surprised me how easy it was to break into the Password class even though I didn't modify the Textbox or Password object in any way.

On the third hand, it makes my head hurt unsure.gifunsure.gif

Yeah, me too.

Password Hack.zip

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
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
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.