Jump to content

Need help with inheritance and encapsulation


Recommended Posts

Hi,

I'm not clear about the distinction between LVOOP, GOOP, and OpenGOOP so please forgive me if I'm posting in the wrong place -- I am asking about LVOOP in LV8.2 in this posting.

I have been programming in LabView for some time and I have a little experience with OOP in other languages (Python, C++, Java, ...) but I am a scientist with no formal training in programming -- I just figure it out as I go. I know from long experience that it is important to figure out how the LV developers intended for you to use various tools.

I am experimenting with using LV8.2 classes to implement dynamically loaded support for devices. After a lot of frustration, I figured out how to dynamically create an instance of a class using the DefaultInstance member of the LVClassLibrary property (the hardest part was figuring out what to wire into it). I wire the path to the class definition (class_name.lvclass file) into the App->LVClass.Open method to get a reference used to generate the default class instance.

At the top of my hierarchy (PARENT), I have Device.lvclass which defines data and methods required for every device (data = device_name, device_class, device_type, device_description,

Link to comment
I'm not clear about the distinction between LVOOP, GOOP, and OpenGOOP so please forgive me if I'm posting in the wrong place -- I am asking about LVOOP in LV8.2 in this posting.

You've come to one of several right places. ;-) ni.com would also be a good place. I'm the lead developer for LabVOOP (my preferred acronym -- LVOOP doesn't have any clear way to pronounce it).

Here are two docs that are likely to help you in some degree. Neither of these ships with LV -- they weren't finished until after the final CD was done.

The LabVIEW Object-Oriented Programming FAQ

http://zone.ni.com/devzone/conceptd.nsf/we...62571D800665A4D

LabVIEW Object-Oriented Programming: The Decisions Behind the Design

http://zone.ni.com/devzone/conceptd.nsf/we...62571CA00506D74

I am experimenting with using LV8.2 classes to implement dynamically loaded support for devices. After a lot of frustration, I figured out how to dynamically create an instance of a class using the DefaultInstance member of the LVClassLibrary property (the hardest part was figuring out what to wire into it). I wire the path to the class definition (class_name.lvclass file) into the App->LVClass.Open method to get a reference used to generate the default class instance.

Hrm... that's not quite right. "How to dynamically create an instance..." You're going to want to read all the sections of the above documents dealing with "by reference vs by value" and probably several of the forum topics on this website. The solution that you came to is no more nor less effective than dropping an Initialize Array primitive and asking it to create an array of arbitrarily many objects. And you really need to read up on LV2-style globals aka functional globals aka shift-register databases. I don't have links to any of those topics off the top of my head, but search around using those keywords and you'll find good stuff.

1) How do I access the inherited data clusters from within CHILD or JUNIOR? They inherit device_name and the create_device method from Device.lvclass and I am able to invoke the create_device method (using dynamic dispatch) by wiring in an instance of Motor_Controller.lvclass or Motor_Axis.lvclass. I don
Link to comment

Hi Stephen,

Thanks for your reply. I have read the documents you suggested and all is much clearer now. I was quite alarmed to read the editorial comments on the "Factory Pattern" about not working with built applicaions becase the LVClass refnum us unavailable. This pattern is exactly what I came up with for dymanically creating instances of my classes based on reding a config file. I must have described it poorly since you didn't recognise it. We have a strict policy of not running from code (we use built applicaitons only) on our production machines. This couold mean that we won't be able to use LabVOOP in our application enven though it is huge (>500 VI's) and could really really benefit from OOP. The enum trick you mention would be too much of a code management nightmare.

Are there any other possibilities of dynamically creating instances of a class? I have been playing with your idea of not using the DefaultInstance property but rather using the InitializeArray built-in to populate my array (liked list) of objects (class instances). I tried just creating an array of instances of the parent class, but the information needed to choose which child class to invoke from dynamic dispatch doesen't seem to be there. Maybe there is a way to typecast the parent class to be the correct .ctl type for the child -- or since I have the path to the class library from the configurations file, I can dynamically call a VI (method) in the child class (call by VI reference?) that takes no inputs but returns the default cluster (name.ctl) and use that as the instance of the class (in the spirit of a contsructor). This sould be a static method?

Also, I'm still not clear on the inheritance of the data (I'm sorry, sometimes I just don't get it). Going back to my original post and the idea that an instance of a child class inherits the data clusters of its ancestors. This is implied in several places in what I have read. But is it really the case that you can't access this data in a child method but rather you need to add a method the ancestors to expose this data to the child? Perhaps I am cofused by classes only having private data in LabVOOP.

--Lee

Link to comment

I'm going to answer your question on data inheritance first... I'll get to your "alternate ways to do dynamic instantiation" question in a moment.

Also, I'm still not clear on the inheritance of the data (I'm sorry, sometimes I just don't get it). Going back to my original post and the idea that an instance of a child class inherits the data clusters of its ancestors. This is implied in several places in what I have read. But is it really the case that you can't access this data in a child method but rather you need to add a method the ancestors to expose this data to the child? Perhaps I am cofused by classes only having private data in LabVOOP.

Every instance of the child has the parent's data. But although the child class carries this data around with itself, no function owned by the child is allowed to read or modify the data.

You said you speak C++... so let me write some equivalent code.

post-5877-1157742123.png?width=400

Ok... in the above code, is there any way for any function to ever change the value of x ? The answer is no. Every instance of Child will have two integers inside it -- x and y -- but there is absolutely no function that has the authority to change the value of one of those integers. If Child is going to have access to x, then Parent needs to supply get and set functions... like the ones shown in the red box:

post-5877-1157742198.png?width=400

This same idea of get and set methods is required in LV.

Make sense?

Are there any other possibilities of dynamically creating instances of a class?

I just realized there's a possible terminology collision here. I've been assuming one particular meaning, but I'm not sure that meaning is what you intend, given the other the details in your latest post.

You use the phrase "dynamically creating instances of a class."

Do you mean

A) "Assuming a class definition is already in memory, are there any other possibilities for creating instances (objects) of that class?"

or do you mean

B) "Are there any other possibilities for dynamically loading a class definition into memory and then creating data of that class type?"

If the answer is A:

Every time you fork a wire you create a new instance of your class. Dynamic creation is anything that creates a new copy of your data -- such as Initialize Array. Now, if you need specific instances of your object that you can talk to using a reference model, then you should take a look at the ReferenceObject example that ships with LV:

<labview>\examples\lvoop\ReferenceObject

After you've looked at that example, you should read this discussion forum:

The version that ships with LV is a viable implementation of references, but it is one among many.

If the answer is B:

Yes, there is another way to achieve dynamic loading of classes into memory, without the enum suggestion.

Any VI that uses a class will load that class into memory. You can use VI Server to load a VI into memory even in a built application. SO....

  1. Create your classes. Let's call them Alpha, Beta and Gamma. These classes do not have to be related.
  2. Add a method named DefaultValue.vi to each of these three. This VI should have no inputs and its only output should be of type LabVIEW Object (or, if there is an inheritance relationship among your classes, any class that is a common parent to all three classes).
  3. On the block diagram of these classes, drop a constant of the class type and connect it to the output FP Terminal.
  4. Your Factory code should be changed now to use Open VI Reference. Instead of loading a class directly, it will load any of the DefaultValue.vi versions. Use a Call By Reference node to invoke the VI and get the data instance of the newly loaded class.

Link to comment

Stephen,

I've got it!!!! I can't thank you enough but the light bulb is on now.

Thnaks to your very good C++ example code, I now see how inheritance of PRIVATE data works in LVOOP. It is completely clear.

You use the phrase "dynamically creating instances of a class."

Do you mean

A) "Assuming a class definition is already in memory, are there any other possibilities for creating instances (objects) of that class?"

or do you mean

B) "Are there any other possibilities for dynamically loading a class definition into memory and then creating data of that class type?"

The answer is B.

If the answer is B:

Yes, there is another way to achieve dynamic loading of classes into memory, without the enum suggestion.

Any VI that uses a class will load that class into memory. You can use VI Server to load a VI into memory even in a built application. SO....

  1. Create your classes. Let's call them Alpha, Beta and Gamma. These classes do not have to be related.
  2. Add a method named DefaultValue.vi to each of these three. This VI should have no inputs and its only output should be of type LabVIEW Object (or, if there is an inheritance relationship among your classes, any class that is a common parent to all three classes).
  3. On the block diagram of these classes, drop a constant of the class type and connect it to the output FP Terminal.
  4. Your Factory code should be changed now to use Open VI Reference. Instead of loading a class directly, it will load any of the DefaultValue.vi versions. Use a Call By Reference node to invoke the VI and get the data instance of the newly loaded class.

I tried this scheme before I entered my long post on 8SEP06. I could not get it to work becuae I was still confused about data inheritance. For the Open_VI_Reference buil-in I just picked one of the child "DefaultValue.vi"'s at random for the type specifier to get the strictly typed VI. Then when I would try to call DefaultValue.vi for a different child, I would get the error the VI invoked by "call by VI reference" didn't have the same connector terminals as the strictly typed VI. But after reading your post I realized that if I put a DefaultValue.vi method in my top level parent (Device.lvclass) then it will work becasue the data type is compatable with all of the offspring. Its too bad the children can't just inherit the DefaultValue.vi method from Device.lvclass, but if you do this the instaces created don't have the information about which child they are an instance of (sorry for the bad grammar). If I put a "DefaultValue.vi" type method in every child, then tehy all have to have unique names which might lead to trouble down the road when we merge in code developed by other groups.

Thank you very much for your help.

--Lee

Link to comment
Its too bad the children can't just inherit the DefaultValue.vi method from Device.lvclass, but if you do this the instaces created don't have the information about which child they are an instance of (sorry for the bad grammar). If I put a "DefaultValue.vi" type method in every child, then tehy all have to have unique names which might lead to trouble down the road when we merge in code developed by other groups.

The VIs in each child do not have to have a unique name as long as there is no VI of that name in the parent class. For example:

Class Parent has two children, Kid and Junior.

Kid can have a static method DoStuff.vi.

Junior can also have a static method DoStuff.vi.

The above is true as long as Parent does *not* have a method named DoStuff.vi. If Parent does have a method named DoStuff, then it must either be dynamic (and Kid and Junior become overrides) or, if it is static, then Kid and Junior cannot have a VI with that name.

Link to comment
You use the phrase "dynamically creating instances of a class."

Do you mean

B) "Are there any other possibilities for dynamically loading a class definition into memory and then creating data of that class type?"

If the answer is B:

Yes, there is another way to achieve dynamic loading of classes into memory, without the enum suggestion.

Any VI that uses a class will load that class into memory. You can use VI Server to load a VI into memory even in a built application. SO....

  1. Create your classes. Let's call them Alpha, Beta and Gamma. These classes do not have to be related.
  2. Add a method named DefaultValue.vi to each of these three. This VI should have no inputs and its only output should be of type LabVIEW Object (or, if there is an inheritance relationship among your classes, any class that is a common parent to all three classes).
  3. On the block diagram of these classes, drop a constant of the class type and connect it to the output FP Terminal.
  4. Your Factory code should be changed now to use Open VI Reference. Instead of loading a class directly, it will load any of the DefaultValue.vi versions. Use a Call By Reference node to invoke the VI and get the data instance of the newly loaded class.

I don't think there is much idea in just creating this kind of dynamic factory for general LabVIEW objects since general LabVIEW objects cannot share any methods. You end up in a wire of LabVIEW object but you cannot call any methods since general LabVIEW object doesn't have any methods. So the wire only passes private data you cannot access. The only thing you can do is really flatten it to string and store the flattened object to a file or send it to network.

There is much more logic if you create a parent class for all your possible classes which defines at least some dynamic methods and are dispatched to the correct class when called. You probably want a getType method to retrieve the type (class) of the object. Then depending on your application you probably want also some other methods; your application must use the objects somehow and you must write common interface (i.e. parent class dynamic methods) for all your classes so that your application can use these objects at all.

I also think it's better idea to dynamically (call by reference node) call a VI that not only returns an object but also initializes the object so that the object really represents something (of course your objects may not need initialization, but often they do). You pass same initialization parameters for all of these dynamically called VIs and each of these VIs takes care of initializating one class type based on the parameters you pass to it.

To create different types of objects, you must select at runtime which of these VIs you call. You pass this VI information required for the VI to initialize your class object. The VI takes care of the object initialization and returns an object of your parent object type. The parent object is ancestor of all your possible objects and which defines the method interface that all your objects share (i.e. all your objects have common methods with same name and same connector pane but different functionality). Then you use the created object by calling these inteface methods. Each interface method is dynamically dispatched to the correct class type method automatically.

p.s. I didn't read all of the thread, so I'm sorry if I misunderstood your coding problematics.

Link to comment
  • 4 weeks later...

Hi,

I finnally got around to trying out Stephen's idea for working around the problem of dynamically creating a class instance that will work with the application builder. The code runs as a built application but I do not get the results I expected. I will atempt to attach a zip file with my test project to this post.

In the example I created 3 classes. The top anscestor is called 'Alpha', then there is 'Beta' which is a child of 'Aplha' and 'Gamma' which is a child of 'Beta'. Each class as a 'Create_Instance' method that just returns an object (instance) of that class. The 'Create_Instance' method for the class gets called for the chosen class (see menu control on test.vi) gets called via a 'call-by-VI-reference' node. The object returned then gets fed into a dynamic VI called 'Identify' which exisits in all 3 classes. Dynamic dispatch should now select the version of 'Identidy' that is a member of the class corresponding to the object (instance) created by the 'Create_Instance" call. Insted, you always get the version of 'Identify' corresponding to the 'strictly typed VI' used for the 'Open VI Reference'. Evidently, the object returned by the "Create_Instance' method gets coerced into an object of the type on the output terminal of the VI chosen for the strictly typed VI. How do you prevent the coercon??? I get errors about the terminal types not matchiong for the 'call-by-VI-reference' but the code still runs

Thanks,

--Lee

Download File:post-3444-1160078682.zip

Link to comment
How do you prevent the coercon???

:D You don't prevent the coercion! You embrace it!

I've been meaning to post these for the last couple days, but I haven't gotten around to it. There's a write-up that goes with them, but it isnt' ready yet. But take a look.

This is the original solution that doesn't work in a runtime engine:

http://www.outriangle.org/jabberwocky/Fact...ingClassRef.zip

This is the modified solution that should work in a runtime engine:

http://www.outriangle.org/jabberwocky/Factory_UsingVIs.zip

In both cases, open the enclosed .lvproj file and then take a look at "DEMO Factory.vi". You can explore from there.

Link to comment

Stephen,

It works!!! I studied your example and at first I could not figure out why yours worked while my code crashed and burned. I finally realozed that your "Load_Plugin" VI was coercing the offspring to the type for the "Generic" (base, parent) class. I was trying to return an object of the type for the child. Now I understand your previous comment!

:D You don't prevent the coercion! You embrace it!

I image this means that the Labview equivalent of the C++ "Run Time Type Information" (RTTI) is preserved after the coercion.

Link to comment

All,

Now that I understand how to dynamically create an instance of a class, I am back to one of my original questions:

Imagine that I am writing instrument control software for a piece of equipment that has 4 motor controllers each controlling 8 motor axes. For simplicity, let us assume that all 4 motor contrallers are of the same type. A good model for structuring the device interface part of the instrument control software would be to create a parent (base) class called device.lvclass that sits above all the supported hardware. A child of device.lvclass would be motor_controller.lvclass which would handle all the methods for the motor controller that are not specific to a particular motor axis (e.g. the IP address of the controller). There would then be a child of motor_controller.lvclass called motor_axis.lvclass that handles operations that are specific to an axis (e.g. motor speed, motor velocity, direction, etc.).

One would create 4 instances of motor_controller.lvclass and 32 instances of motor_axis.lvclass.

After each instance of motor_controller.lvclass is created and initialized, I give it a name and store it for future use.

Now, when I create an instance of motor_axis.lvclass and initialize it, one of the parameters I read from the configuration file is the name of the motor controller that the axis is associated with. When it comes time to drive the motor that a particular instance of motor_axis.lvclass represents, the "move_motor" method of motor_axis.lvclass constructs a command to drive the motor to the erequested destination and then invokes a method on the associated motor controller to execute the command. There are two obvious ways one can do this. First, you can find the correct instance of motor_controller.lvclass using the device name of the motor controller for this axis and call the execute_command method with the correct data object wired in. Or, you can use the fact that motor_axis.lvclass is a child of motor_controller.lvclass and therefore inherits the data members of motor_controller.lvclass. This means that when an instace of motor_axis is created and the device name of the associated motor controller is read from the configuration file, once can call a method on the motor controller to populate the inherited data menebers from motor_controller.lvclass for the correct motor controller. Now when one needs to invoke a method on the motor controller for a given axis, you just have to pass up the instance of the motor axis and it will contain the data for the motor controller.

Which is the preferred way to do it?

For the second method:

What is the best way to copy the data from the correct instance of motor_controller.lvclass into the instance of mtor_axis.lvclass?

What if a motor controller parameter gets changed -- How does the change propogate to all of the child instances of motor_axis.lvclass for that motor controller?

--Lee

Link to comment
  • 3 months later...
:D You don't prevent the coercion! You embrace it!

I've been meaning to post these for the last couple days, but I haven't gotten around to it. There's a write-up that goes with them, but it isnt' ready yet. But take a look.

This is the original solution that doesn't work in a runtime engine:

http://www.outriangle.org/jabberwocky/Fact...ingClassRef.zip

This is the modified solution that should work in a runtime engine:

http://www.outriangle.org/jabberwocky/Factory_UsingVIs.zip

In both cases, open the enclosed .lvproj file and then take a look at "DEMO Factory.vi". You can explore from there.

Hi Aristos Queue

Forgive

:D You don't prevent the coercion! You embrace it!

I've been meaning to post these for the last couple days, but I haven't gotten around to it. There's a write-up that goes with them, but it isnt' ready yet. But take a look.

This is the original solution that doesn't work in a runtime engine:

http://www.outriangle.org/jabberwocky/Fact...ingClassRef.zip

This is the modified solution that should work in a runtime engine:

http://www.outriangle.org/jabberwocky/Factory_UsingVIs.zip

In both cases, open the enclosed .lvproj file and then take a look at "DEMO Factory.vi". You can explore from there.

Aristos,

Forgive me for barging in on this old thread. I've been trying to read along with this old conversation for study purposes. The examples you specified above, could they be found anywhere as I'd like to look them over.

(I am having trouble getting access to them here)

Thanks in advance,

John

PS: Thanks for sharing, You guys are amazing!

Well I just figured it out

Duh!

No need to help thanks!

Link to comment
  • 5 weeks later...

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.