Jump to content

XControls as plugins: this is how we do it(?)


Recommended Posts

Note: I first posted this on NI's forum, but then I thought there might be more interested people here. (I made two posts there, and combined them here; that's what parts 1 and 2 are.)

There are almost certainly some wrong statements below -- corrections are eagerly solicited.

By the way, I'll be at NI Week next week in the Texas Instruments booth. If anybody wants to talk some LV smack, I'm all over it. :)

Part 1: the problem

We want to dynamically load and manipulate XControls -- that is, we want to dynamically load an XControl and invoke properties and methods on it.

Not only that, but we want to be able to dynamically load one of several XControls, all sharing an interface, just as one might dynamically load different VIs that have the same connectors. (Why? So we can make an app that loads "plugins" that can have independent event loops, properties, and state -- in other words, we want objects -- which is why we want XControls as plugins.)

Now, we know that we can't dynamically load XControls. So we don't. Instead, we dynamically load a VI that has our XControl in it. This "wrapper VI" has one output connector, which is a refnum for the XControl inside it. We downcast the refnum to the XControl's class and use it to get property and invoke nodes for the XControl.

A big problem is that LabVIEW has its own way of locating the XControls themselves, and we're not sure what that is. So we have to trick LabView into loading the XControl we want, observing the following rules, which we have found out by experiment (and may be incorrect):

1. LabVIEW locates XControls by name only, even though it stores paths in calling VIs anyway. (The same applies for subVIs.)

2. LabVIEW seems to load the nearest XControl to the VI that loads it. For example, if a VI loads XControl "foo.xctl", and foo.xctl is in the same directory, it will always load that one. It will not load a foo.xctl in a different directory, because it uses a nearest-neighbor search. (Or so we hope.)

2a. When an XControl is loaded, you cannot load another XControl with the same name. (This is also the same as with subVIs.)

2b. If an XControl called "foo" is in memory, and you try to load a different XControl also called "foo", LabView won't do it. It will use the "foo" already in memory.

3. To load a different XControl that has the same name, you have to unload all instances of XControls first, which you do by unloading all VIs that reference that XControl.

We're not completely sure about Rule 4 yet, but we're 90%:

4. SubVIs aren't the only things that can cause XControls to be loaded. Class constants, invoke nodes, property nodes, and possibly reference wires also cause XControls to be loaded.

So, if I make a VI "bar" that has a property node for XControl "foo", but I don't actually place "foo" in VI "bar", "bar" will still load "foo", even though "bar" doesn't actually have "foo" in it. It's enough that "bar" has a property node for "foo".**

Rule 4 is what makes our lives miserable. To work around it, we will make subVIs which contain property nodes for "foo", and dynamically load these subVIs. This way, we can keep all references to "foo" out of our shell VI, and force (well, convince really) LabView to unload an XControl so we can load a different one when we want.

Part of the above is demonstrated in the attached code, which is explained in Part 2.

* apologies for Super Mario Bros. reference

** Further evidence in support of Rule 4: I made such a VI and looked at the VI file in a text editor. I found my XControl's base name embedded in the binary rubble. I think it's a sign.

Part 2: our solution

The code promised in my last post is attached in "foofun.zip". It has the following things:

- Two XControls, each named "foo". One is in folder "foo1", the other in folder "foo2". Although named the same to LabView, we will call them foo1 and foo2.

- Each XControl directory has a wrapper VI called "foo.vi" which puts out a reference to the XControl it contains. - A VI called "samefoo.vi", explained below.

- A VI called "anyfoo.vi", explained below.

- A property wrapper VI called "prop_whoareyou.vi". This is used by anyfoo.vi and is explained below.

The XControls

The XControls called "foo" each have a label. In foo1, the label reads "This is foo1"; in foo2, the label reads "This is foo2".

Each "foo" also has a property called "whoareyou". This property is read-only and returns a string. In foo1, the string is "foo1"; in foo2, the string is "foo2".

samefoo.vi

samefoo.vi loads the wrapper VI of your choice into a subpanel. The wrapper VI contains the XControl and outputs a reference to it. We downcast this reference to the type of the "foo" XControl. Normally we would use this reference to invoke properties etc., but here we don't do anything with it, so that we can demonstrate the evil side-effect of a class type specifier. More on that in a bit.

When you run samefoo.vi, it presents a file dialog. Select one of the wrapper VIs -- either foo1/foo.vi or foo2/foo.vi. (If you choose a different one, you will get a type mismatch error.)

Now, we would like foo1/foo.vi to load foo1, and foo2/foo.vi to load foo2, but that's not what happens. Instead, both wrapper VIs give us foo1. It doesn't matter which one we load first: we always get foo1.

Why does this happen? We're not positive, but we're pretty sure that the culprit is the class type constant. This causes LabView to store some kind of reference to an actual XControl (rather like a static VI reference). In this case, I used foo1 to get the class name, so that's the one that samefoo.vi is eternally stuck with.

anyfoo.vi

As the name implies, "anyfoo.vi" is our solution to the "samefoo.vi" problem. Like samefoo.vi, it has a subpanel and loads a wrapper VI into it. Unlike samefoo.vi, it will load either foo1 or foo2, depending on the wrapper you choose. It can also call the "whoareyou" property on either foo1 or foo2 and get the correct result.

anyfoo.vi does this by not including a class type constant or a property node (the latter has the same pernicious effect). Instead, it uses the class type constant and property node in prop_whoareyou.vi. That VI's purpose is to call a property on an XControl in a wrapper VI. It takes as input a reference to the wrapper VI, calls the promised property, and outputs the result.

Note that the prop_whoareyou.vi is dynamically loaded. We can't use the VI itself, because that would cause anyfoo.vi to be stuck with a reference to foo1 or foo2. We can't use a static reference for the same reason.

The key is load order. anyfoo.vi loads first, when we open it. It has no references to XControls, so no XControls get loaded. When we run anyfoo.vi, it loads one of the wrapper VIs first. This loads the XControl we want. After that, we load the property wrapper VI. There only needs to be one of these, because even though the property wrapper VI has references to one of the foo XControls, it will always use the foo that's already in memory.

Therefore, it's critical to load the wrapper VI before the property wrapper VI. Otherwise, either of the foos will load, we won't be able to choose which one, and it will be stuck in memory until the property wrapper VI is unloaded.

The verdict

Apparently we can get the effect we want, but we have to resort to evil chicken-wire-and-chewing-gum trickses, and the process is very inefficient. But it's the best we can think of. If anybody knows of a better or more efficient method, we'd love to hear about it.

(BTW: I suppose we could have used GOOP, but I've looked at that before, and this seemed so beautiful and nice at first ..)

Download File:post-5729-1154486002.zip

Link to comment
---snip---

Not only that, but we want to be able to dynamically load one of several XControls, all sharing an interface, just as one might dynamically load different VIs that have the same connectors. (Why? So we can make an app that loads "plugins" that can have independent event loops, properties, and state -- in other words, we want objects -- which is why we want XControls as plugins.)

---snip---

mpa, if you listen to the Mistaken Podcast you will know that a native OO in LabVIEW is not too far in the future. It might be worth while to wait for it.

-Franz

Link to comment
mpa, if you listen to the Mistaken Podcast you will know that a native OO in LabVIEW is not too far in the future. It might be worth while to wait for it.

Might be, but we hadn't heard about it when we were designing our plugin scheme, and my deadline doesn't seem to be going anywhere.

Hopefully it won't be too late to integrate the new stuff when it comes out, if it's useful. It would be very cool if it made my post here useless. Maybe I'll find out next week, eh? ;)

Link to comment
1. LabVIEW locates XControls by name only, even though it stores paths in calling VIs anyway. (The same applies for subVIs.)

2. LabVIEW seems to load the nearest XControl to the VI that loads it. For example, if a VI loads XControl "foo.xctl", and foo.xctl is in the same directory, it will always load that one. It will not load a foo.xctl in a different directory, because it uses a nearest-neighbor search. (Or so we hope.)

2a. When an XControl is loaded, you cannot load another XControl with the same name. (This is also the same as with subVIs.)

2b. If an XControl called "foo" is in memory, and you try to load a different XControl also called "foo", LabView won't do it. It will use the "foo" already in memory.

This is normal behaviour. LabVIEW stores an object (VI/CTL/XCTL) as a RELATIVE path, and if it don't succeed in finding such an object it uses the search sequence you can heavily tweak! (in options/paths/search paths)

Then it has all the xcontrols in one namespace so only one can be loaded.

What you could do is put the xcontrol in a library (foo1.lvlib:foo.xctl and foo2.lvlib:foo.xctl). I'm not so positive about lvlibs right now but maybe in the future (near or not so near :D ) they improve!

Ton

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.