Jump to content

Critical Analysis and Alternative Implementation of Object Oriented Programming in LV 8.20


Recommended Posts

Hi all,

I've written a critical analysis on the implementation of object oriented programming in LabVIEW 8.20. In the same document I propose what I think is a better way to implement object oriented programming in LabVIEW. Check the document out. I'd like to hear your comments.

It has been discussed that LabVIEW Object Oriented Programming should be based on objects by reference. I strongly disagree the objects-by-referense should be the basis of the implementation. The reason is that objects-by-value are much more efficient and allows utilizing the concurrecny capabilities of LabVIEW. However I think that even though objects-by-value should be the basis of the implementation, there must be a way to create by-reference objects to refer to objects of the outside LabVIEW world like files and windows and data-acquisition devices etc. I also think that the leaving out constructors, copy-constructors and destructors was a very bad decission. I also cover some other issues, but have left out a few important issues like exceptions due to time limitations (I'm currently on vacation...)

Link to the document

p.s. I don't have a PDF version of the document available yet. My primary computer suffered from heat and this laptop doesn't have PDF conversion software installed. If anybody is ready to provide the PDF, please email it to me. The contact information can be found from the link above. Edit: I added a RTF for the moment for those who cannot read Word documents.

-jimi-

Link to comment

Wow, nice work you have done. I like your ideas.

I haven't made my oppinion on which OO method suits my needs better: LabVOOP or the various GOOP's. There is one thing that bugged me since the first 10 minutes I glanced into LabVOOP: how do one access object data in parallel loops?

So far I have on suggestion on your proposal:

On page 21 (constructor block diagram) you define a class constant and then you invoke explicitly the constructor of the parent class. But you could also replace the class constant with the parent constructor. In all my implementations I did in Delphi the first command of a constructor is a "inherited create" (calling of parent constructor).

Didier

Link to comment

IMO the LV8.2 objects are just strict type defs with their own sets of wires that only VIs "belonging" to the wires can act on. Or in other words, some sort of protected clusters, since they only can be called by value. However, they also have inheretance and overriding. All in all, considering that they shall be used in a data flow setup, and be consistent with all the other existing types as well as the data flow paradigm, i think NI has done a good job.

In a data flow language there are no need for explicit constructors/destructors and copy methods, because all this is an implicit part of the language (or rather a non-existing part, because there are no variables in the normal sence of the word).

I think LV8.2 objects function exactly as they should. The main problem is that LV is a is graphical DATAFLOW language, and not the GENERAL graphical language that most of us would like that it should be. What it lacks most is an effective way to store data, and effective pointers and references.

Link to comment
In a data flow language there are no need for explicit constructors/destructors and copy methods, because all this is an implicit part of the language (or rather a non-existing part, because there are no variables in the normal sence of the word).

This is an argument I've seen number of times, that is dataflow implies lack of constructors, copy-constructors and destructors (or implicit memory only constructors). I fail to see why dataflow would imply no need of constructors or inability to implement constructors.

One can always allow no constant objects on a block diagram nor on front panel i.e. require that all objects are properly constructed using constructor call. When ever an object is placed on a block diagram, a constructor creating the object is actually placed instead. When ever an object is placed on a front panel as a control, also constructor is placed instead, so that instead of private data elements, constructor parameters are visible on the block diagram. These front panel objects would differ from the block diagram objects in a sense that when the VI is called as a subVI then constructor is not called.

That is when ever object buffer is created, it is created not only by reserving a block of memory and initializing this block to default values but instead reserving a block of memory and calling some code to do the initialization. I see no reason what so ever that in dataflow paradigm would not allow this.

Link to comment
That is when ever object buffer is created, it is created not only by reserving a block of memory and initializing this block to default values but instead reserving a block of memory and calling some code to do the initialization. I see no reason what so ever that in dataflow paradigm would not allow this.

I don't think anything prevent you from doing this, either by normal programming or by using XControl at those places where you need such functionality. But the main point is that in dataflow "objects" are created when data for the "object" (controls) arrives, or at branches (copies) and then everything is 100% determined.

Link to comment
I don't think anything prevent you from doing this, either by normal programming or by using XControl at those places where you need such functionality. But the main point is that in dataflow "objects" are created when data for the "object" (controls) arrives, or at branches (copies) and then everything is 100% determined.

An "empty cluster" is always "100% determined" but the problem is that it just cannot represent the data I'd like it to represent (well it represents nothing). Similarily uninitialized objects don't necessarily represent anything meaningful unless initialized with class programmer defined code. The code could do tricks such use opening a file. An object representing a file that doesn't point to a file isn't really 100% determined to me.

What we need to do is that we need to distinguish between the class developer and the class user. The class developer is responsible to develop such a class that is hard to use inproperly by the class user. When we have no constructors, it is fairly easy for the class user to create an object of that class that is not properly initialized.

Custom class methods are not a solution. If the language doesn't force their call, the class user can always forget to call them. Even if the user remebers to call the class initialization method, problems arise. A child class cannot override parent class method interface. That is child class cannot use the same initialization method name if it requires more parameters or different parameters than the parent class. Think of a class hierachy of ten levels. All of the levels would have different name for the initialization function. Do you think that the user really remembers to always call the right initialization function. I bet users would call the wrong functions quite often.

Then think of the situation you want to change the implementation of your class. You need to do some initialization stuff to create proper objects of the class. Let's say you want to open a file to store very large arrays that don't otherwise fit into the memory. The problem arises since LV doesn't force the call of default constructor. All the code using the class needs to be properly modified to call the custom initialization method. It's very hard to do this since the code looks like ok, there are no broken wires because calling the initialization mehtod is volontery.

I could go on this list.... The fundamental thing however is that in object-oriented programming, the objects should ALWAYS represent a valid entity, there should be no uninitialized objects, not even transient uninitialized objects. All objects should be properly initialized on creation or on copying. LabVIEW doesn't guarantee this, or well it guarantees it only for in-memory objects that do not interact with the surrounding word. With surrounding word I mean files, other programs, screen, acquisition devices, network or anything that is not only present in LabVIEW internal memory.

Link to comment

I still think you are pointing to a shortcoming that does not really exist in a call by value - data flow "world". The object may not neccesarily be pointing to a file, rather it would be the file itself or more precisely the data in that file. The file path or ref may only be a part (input) of the method "write data" or "read data". Still, in that data object there may very well be a control for the file ref or path, so that the cluster looks like:

[DATA]

[file ref]

The "initial" or default values would be empty data and "not a ref". IMO it would be just as strange to initialize the file ref to anything else than "not a ref" as it would be to initialize the data to anything else than empty data (all objects are always 100% determined since they are call by value).

Now, if you want something else, you just write a member VI that initiates the object with whatever values you want. There is no need to create the object in any form upfront, the VI inputs all kinds of non-object data and output the object itself. A "read data" or "set data" method may therefore be all the constructor you need. If you use by ref, you will have to create the *ref* before you can create the object. And since a ref without an object is totally meaningless you will be 100% sure that all refs points to valid objects, and therefore you need a constructor.

The only time i see that this may in some strange cases cause problems is if the object is created as a constant and used directly with no other considerations, but this would be the same as opening a call by ref object with default parameters on the constructor, or use an arbitrary floating point constant in calculations.

Link to comment

I strongly disagree with bsvingen. Consider for example a class representing an employee with fields with private data members name, age, sex, email, salary etc. I don't think that an initial LV object of this class can be considered an object representing an employee. No static initial values can represent a valid employee. What would we mean by a default employee. What would be his or her salary. Would default employee be male or female. This just doesn't make sense. That is and init method is needed to initialize an object of class employee to validly represent an employee, let's call this method Init.

Consider now that you derive a class executive from the class employee. In addition to employee data members, let's add an object representing option program the executive belongs to. Now to initialize the executive class object, we cannot use the name Init anymore, since the front panel layout of this method is fixed and cannot be changed. We need more information to initialize the executive object. We need to create a new initialization method and call it InitExec.

What happens if class user accidentally calls Init instead of InitExec when he creates a new instance of executive. According to LabVIEW valid instance of executive class is created and no error is generated. However the object is not correctly initialized, since the option program data member points to some default option program. This mistake is more than likely to happen, if there is not a common name for initialization method (i.e. a constructor).

NI emphasizes that it made the design decissions it made with LabVOOP so that LV would stay simple and intuitive to use. I simply cannot see how allowing class users to use inproperly initialized objects makes it clear and intuitive for the class users. Constructors don't complicate the class development at all, but it simplifies the life of the class user. The class user doesn't need to always check what is the proper way to initialize the class.

However, I also think that the by-reference objects are needed and in this sense the whole discussion about if constructors are needed only for by-value objects is pointless if by-reference objects will be implemented.

Link to comment

As long as the connector pane is the same, we still can use the same init name for the init method for all inherited members. I see that it will be a rather irritating restriction to keep the connector panes equal, especially when theres alot of inherited functions, and you want to change one of them, but it will work (you will have to wire a correct object constant to the init function though, and therefore i don't think it is vise to initialize objects this way, but use the overriding mechanism for other tasks such as plug-ins for instance). Another option is to use the "to more specific class" in the more specific init functions (with specific names and without dynamic dispatch), that way you cannot use the wrong init function, even by accident due to different connector panes, and you don't need to wire the correct object into the function, and you allways get the correct object out.

But the main point still remains, you do not need to explicitly initiate objects in an "init" function when all objects are call by value and inside a data flow paradigm environment like LV. The objects will be initiated at first use no matter how you look at it, because they aren't really initiated in the correct sence of the word, they are only given values since they already exist in the VIs as controls or indicators just like any other LV types. You just make a VI that takes the input you need, and out pops the correct object.

Link to comment
The objects will be initiated at first use no matter how you look at it, because they aren't really initiated in the correct sence of the word, they are only given values since they already exist in the VIs as controls or indicators just like any other LV types. You just make a VI that takes the input you need, and out pops the correct object.

Ok. I agree with this. We just seem to define concepts "initialize" and "100% determined" in different way. Whereas I look the issue from the "object as a meaningful entity" point-of-view, you have a more pragmatic "valid dataflow object" perspective.

Link to comment

I agree that if initialisation is needed depends on the concept. In LV (and where it doesn't go to the outside world) there is no initialisation method of any datatype. But from the moment the outer world is concerned also NI always uses sort of initialisation/clean-up (see file, daq, visa, ...).

OO programming is not only a way to put fancy boxes around parameters and methods, but a concept where one tries to encapsulate everything that belongs to an object.

Inheritance in OO programming is a way to generalize the access to an object, just like IVI. You define a standardized interface for every oscilloscope, but in the background you have child classes specialied for e.g. leCroy, HP,... There is (at least) 1 Method in your code that is of the lowest class of your hierarchy in your code, and this is the constructor (at some point in your code you must choose which oscilloscope you want to work with).

To my oppinion LabVOOP lacks two main features to be usable in the sense of OO programming:

1. overloading: same vi with different conepane. At the point where you define which sub-class you want to use, you must be able to have overloading, since the different sub-classes might need different parameters.

2. get a ref to the data: It makes it much more complicated to write a program in the producer/consumer concept whithout the ability to access your object (the 1 and only instance of the class) from parallel loops. Let me explain: When you have 2 parallel loops, one with event-structure for UI interaction, another with your own states to do the measurement. If you have your Instrument driver packed in a class and you can access your class by reference, you can easily wire the ref into the event-structure and call e.g. the instrument configuration panel by mouse click without interfering your measurement-loop. On the other hand, in LabVOOP one has to define a state in the measurment loop that calls the configuration panel (since branching the class creates a copy) and therefore it interfers into your measurement and you might loose/miss data.

In conclusion I came to the point that the actual LabVOOP is good more for beginner to have an entry point into OO (similar to express-vi's), but when doing bigger programs that have multiple parallel running tasks (like one I did recently with >1000 vi's and 4-8 parallel tasks) LabVOOP is too much restricted to be used as your program structure concept.

Didier

Link to comment

To be more correct, it is an explicit constructor that is not needed because the objects are already made in the VIs as controls or indicators (you still can have "init" functions, but they won't work as constructor functions in ordinary languages, they are just ordinary VIs to fill the object with data).

I disagree with your point that LVOOP is good for beginners. Students today are seldom teached FORTRAN, C/C++ or Pascal. They are teached JAVA and Matlab. When you start with JAVA, OOP is learned from the start (the way OOP is meant to work). Then when they start using Labview i think they will scratch their head more than once trying to figure out how this call by value objects are going to be used. Personally i can see that LVOOP will protect data and make programs more organized (that is also very much due to the project builder), but i still don't see how LVOOP will make the program structure better.

Link to comment

I think almost all LAVA users agree that there are serious problems with the implementeation of LabVOOP. What I suggest is that we bring front and analyze those problems. We then try to innovate the best possible way to implement OOP in context of LabVIEW and dataflow programming. We gather this information into a form of a proposal, prioritize things into a few categories and then hand this proposal to the CEO of National Instruments. What do you think?

Link to comment

I wanted to comment about overloading (allowing same VI name and different conn panes) beeing necessary when inheritance is used. I don't agree. What is necessary is virtual methods, or as NI call them Dynamic Dispath VIs and these are supported. If you need different input/output types you can create different methods. Ok, it is not as neat but it is also less error prone since you know which method gets called.

My second comment is about the native OO solution should be good for beginners. I don't agree. The reason is that when you start using a new technique like OO you like to apply it where it makes most sence and where you see the most obvious need. I believe that is typically the instrument control for the LabVIEW programmers. Creating nice abstractions of data is secondary and more advanced. The problem is that instrument control is where you probably are most likely to prefer by-reference. Therefore the OO beginner will think that the native OO doesn't make sence - it doesn't solve the major use case. Time will tell, but that is a risk I see.

As regards the constructor issue. I agree very much that support for structured initialization is needed. The problem with a constructor in LV is that front panel objects are created when the VI is loaded and that may not me the time it makes sence running the constructor code. For the current solution I think a class template for a structured Init, especially to handle inheritance (allowing different parameters on each level), would make sence. It requires a tool for smooth creation/maintenance of classes though.

/Jan

Link to comment
The problem with a constructor in LV is that front panel objects are created when the VI is loaded and that may not me the time it makes sence running the constructor code.

For what does anybody need to have the Class object as a control, except to pass the class to a sub-vi? If one can paste a control for initialization purpose, we are back these ugly "c programs witten with LV".

Replace the Class object constant with a customizable "constructor-vi" and you are done.

Link to comment
For what does anybody need to have the Class object as a control, except to pass the class to a sub-vi? If one can paste a control for initialization purpose, we are back these ugly "c programs witten with LV".

Replace the Class object constant with a customizable "constructor-vi" and you are done.

If parameters to initialization method (or constructor) would be passed always as a cluster, this parameter cluster could be placed on the front panel instead of the private data members cluster when the class is placed on the front panel. The buffer would be "preinitialized" when the VI is loaded, but when the VI runs for the first time the constructor would be called. If VI is a subVI then constructor is called only if the class control is not connected to an external wire.

Link to comment
Replace the Class object constant with a customizable "constructor-vi" and you are done.

The word you're looking for is "XControl." An XControl of the class type is the initialization that you describe.

And, those of you who don't have Pro edition, forgive me... I didn't know when I posted before that XControls weren't in the base package.

Link to comment
The word you're looking for is "XControl." An XControl of the class type is the initialization that you describe.

And, those of you who don't have Pro edition, forgive me... I didn't know when I posted before that XControls weren't in the base package.

The "XControl bug" (only pro-edition) is very irritating. When i purchased this licence (i have had other licenses in the past from other companies), it was when version LV7.0 just came out. The pro edition back then only had extra stuff for site management and cooperative development that i had little use for, othervise the full development system had everything needed for - well - a "full development". So i purchased Full development system + application builder.

In the last days i have read from NI officials (the white paper about LVOOP and a post in here from NI R&D) that XControl is in fact needed for proper automatic initialisation of objects and that the XControl is "the single reason to upgrade to LV8.0". To me they are saying that the Full Development System is NOT a "full development system" anymore, starting from LV8.0.

Link to comment
The word you're looking for is "XControl." An XControl of the class type is the initialization that you describe.

And, those of you who don't have Pro edition, forgive me... I didn't know when I posted before that XControls weren't in the base package.

XControls can be used for FP initialization of the object, but not for block diagram initialization (you would need XNodes for that). So one shold encapsulate the initialization method separate from the XControl and use XControl just to call the initialization method. I think XControls are far too complicated and slow to develop to just do the object initialization. There should be a simpler and faster way. I hope NI will develop a constructor which has i) block diagram that initializes the object, ii) a typedef that defines the parameters to be passed for the constructor and would also act as the interactive control replacing the need for XControls and iii) front panel that defines the block diagram interface and connector pane for the constructor. There should also be ability to have more than one constructor for each class, however one of them should be default. When placing the class on the front panel or block diagram one could right click the object and select non-default constructor instead of the default one.

Link to comment
I think almost all LAVA users agree that there are serious problems with the implementeation of LabVOOP. What I suggest is that we bring front and analyze those problems. We then try to innovate the best possible way to implement OOP in context of LabVIEW and dataflow programming. We gather this information into a form of a proposal, prioritize things into a few categories and then hand this proposal to the CEO of National Instruments. What do you think?

The map of things you can do with LabVOOP still has "Here There Be Dragons" on it in many places. We've set up base camp and will be exploring out from here. Pointing out which dragon's region you'd like to civilize first would be useful. But I do think that sending the list of priorities to someone on the LabVOOP development team would be more useful than sending them to Dr. T. Just my opinion. :P

In the last days i have read from NI officials (the white paper about LVOOP and a post in here from NI R&D) that XControl is in fact needed for proper automatic initialisation of objects and that the XControl is "the single reason to upgrade to LV8.0". To me they are saying that the Full Development System is NOT a "full development system" anymore, starting from LV8.0.

Both of those sources you cited are from me personally, so please don't hang "NI" with that rope. You don't need XControls for classes or to make useful apps with classes. So far I have four good size apps where every VI is part of a class and none of those have XControls anywhere in them. So you can do a lot of development (one might even say "full development) without XControls. I see many UI/initialization aspects of classes that can be simplified with XControls, but those aspects can be worked around. Christina Rogers' refactoring of the Getting Started Window is all about UI, but doesn't use the XControls. I try not to advocate tools that are only available in Pro as The Solution on general sites like this precisely because not everyone can get the tool (on LAVA/info-LV I'm less concerned about things not in Base package). In this case, I didn't know.

Link to comment

In my first approach to the LV OOP in LV 8.2 I tried to implement a really simple task:

post-885-1156495805.png?width=400

myClass (or TestClass in the LV Example) is a very simple example of a class, which consists of one member (I32) "myMember" and three methods, constructor ->"init", "increment" and "get". On the right side you can see the implementation of this task in C# - I hope most of you are able to read this.

I implemented that in C# and LV 8.2 to compare and I experienced that LV Objects have no mechanism like e.g. C# Objects to store the value of the member-variables automatically. In my understanding a method called on an instance of an object modifies the value of the member and stores it in the member value (not always, but in most cases - of course).

The upper example should work properly, if there were such mechanisms, but it does not. You have to use a shift register (lower example) instead, which leads me to the conclusion, that the LV Objects are in the current release of LV not much more then a protected cluster with *some* features that point in the direction of OOP

And from practical point of view, I miss a possibility to access an instance somewhere else in my code, without the need to drag the wire through the whole and all BDs. Of course I could store the "Object reference" in a Old Style Global, or in a global variable, but excuse me - that thought is really ridicules ...

I hope, that this input has some impact on the further development of LVOOP - I am looking forward to your replies: you're welcome to comment that!

Cheers,

CB

the code:Download File:post-885-1156497555.zip

Link to comment

While trying out the LabVOOP, I searched the LabVIEW help and discovered that this implementation uses flatten data as storage instead of native data format. This could mean that to access only one attribute, LabVIEW still have to unflatten the complete structure?

Quote from help:

LabVIEW stores data as flattened data. The Flatten To String and Unflatten From String functions can handle all class data types. LabVIEW flattens and unflattens the data automatically. While all type descriptors retain the type of data for flattened data types in LabVIEW, the LabVIEW class flattened data itself retains this information as well as class version information. Because the LabVIEW class retains information pertinent to unflattening the LabVIEW class, if the LabVIEW class cannot be found because you move or delete the class, LabVIEW cannot unflatten the data and you receive an error message. Similar to the behavior LabVIEW exhibits when it encounters a missing subVI, you can open the member VI and notice LabVIEW dims the control that is missing the LabVIEW class data. When you load the LabVIEW class corresponding to the broken control, the data is unflattened and the control is no longer broken.

My guess is that this is something that will change in time, because it makes no sense for NI to use flatten data when they have full control of the data handling. Anyway, I think that this is something that should be mentioned in the Critical analysis...

/J

Link to comment

Salut.

LabVIEW is still a "DATA" flow language, where the "DATA" is the center of the problem. I strongly believe in that philosophy and when i heard that they implemented LVOOP i was eager to see what they had done...(i thought that they may had implemented the GOOP way :headbang: ) From my point of view, objects are just containers and should not be the focus of the problem solving...I did not play yet with the LVOOP, but even if i beleive that i will find some rough edged, i know that i will overall like it! :yes:

I believe that to grasp the full potential of dataflow, one as to revize is way of solving problems and not try to force sequential or standard object methodology into it...

Is it possible that the main error with this implementation was to call it LVOOP?

Maybe it should have been called LVAOP (LabVIEW Actor Oriented Programming) like stated in one of the NI-Week sessions i don't remember what morning and by whom, but i liked what i heard there!!! :thumbup:

I will post back when i have went through the documents and a little hands-on with LVOOP...

Link to comment

Wire level reentrancy

New issues seem to pop up. It seems that each class has only one instance of each member method data space. For reentrant methods there is a separate dataspace for each reentrant instance of the method. However dynamically dispatched methods cannot be reentrant. There is defenitely a need for reentrant dynamically dispatched VIs, Aristos Queue already said earlier that NI is trying to solve this issue.

Dataflow OOP has however raised a need for totally new kind of reentrancy, that is wire level reentrant VIs. NI chose to implement OOP in dataflow ideology resulting in LVOOP. In traditional dataflow, wires carry only data. However in LVOOP wires carry also methods and functions. The problem is that in traditional dataflow each wire is totally unrelated to any other wire. In LVOOP wires may share same methods and functions as well.

This raises new kinds of problems. If on one wire a method is called, then all the other wires of the same type have to wait for this method to exit before they can call the same method. This problem can be solved by allowing dynamically dispatched methods to be reentrant in classical way.

The other problem is that shift registers and front panel values become global to the class. Sometimes this is a desired result but in may cases this defenitely is not what programmer wants. Traditional reentrancy would make shift registers and front panel values local to some node on block diagram. This may also be a wanted result, but in a dataflow thinking one may also want the shift register values and the front panel control values to flow with the wire. That is when ever wire is branched, also copy of each VI dataspace is created. This wire level reentrancy is totally new kind of reentracy for which there was no need before dataflow OOP. In this ideology the dataspaces of selected class methods would flow with the wire. Of course the compiler may choose not to copy the dataspace if it is not used or copy only those parts of the dataspace that may be modified elsewhere.

Please NI, pimp my next version of LabVIEW to include wire level reentrant methods.

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.