From Friday, April 19th (11:00 PM CDT) through Saturday, April 20th (2:00 PM CDT), 2024, ni.com will undergo system upgrades that may result in temporary service interruption.

We appreciate your patience as we improve our online experience.

LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

When is dataflow not data flow? Updating LabVIEW Arrays through Call Library Function Nodes?

As someone who has used LV professionally for many years, I think I understand the concept of dataflow pretty well and although I don't use them very often, I thought I understood most of the intricacies of how to use call library function nodes to call external code.

 

However...I came across some code yesterday that I could not believe was possible since it completely breaks LV dataflow principles.

 

An array wire being returned by a CLFN gets magically/asynchronously updated, changing the data on the wire magically in the background.

 

Unfortunately, this is part of a hardware driver and I don't have access to the hardware so I can only post screenshots, but it's part of the driver for PicoScope USB devices and their streaming mode of operation. If you really want to see the LV, download the PicoScope LabVIEW driver.

 

Here is the example from PicoScope (with some minor modifications, I promise there is no funny business going on:

2017-04-28_11-21-40.jpg

 

As you can see 'Start Stream' VI on the left returns an array and then inside the loop it has a 'Get Stream' VI which returns an index/length of that array.  Now according to LabVIEW Dataflow, the values on array tunnel cannot change. So surely this does not work? Even if the index/length values change, the array is essentially a constant at that point so you would never get new data after the 'stream' VI. Well, it does work! I sense black magic!

 

Don't believe me? Here is a screenshot showing the contents of the array for a few loop iterations:

2017-04-28_11-43-38.jpg

 

As you can see, the 'Channel A' array in the cluster is the full array returned by the 'Get Streaming' VI. You can see that for the first two iterations, the values are the same, but the start index/length gets updated. For the 3rd iteration, there array values have been updated and the start index has wrapped back around to 0.

 

Ok, so lets delve into the two VIs to see what is happening...

 

'Start Stream'

2017-04-28_11-27-14.jpg

 

Ok, so this looks pretty standard for a CLFN - you create an array of the appropriate buffer size and pass it into the function and typically, functions like this would fill the array with your data. You have to allocate an array of the correct size or risk memory exceptions. The array parameter 'Channel A' is configured as a 'Array Data Pointer' for both CLFNs.

 

'Get Streaming Values'

2017-04-28_11-35-05.jpg
Aha! Now it starts to make sense what is actually happening...the VI that runs in the loop tells the DLL to 'read data', waits for it to be ready/available and then returns the index/length of the buffer for the new data. BUT WHERE IS THE DATA?!? 
 
Well the only possible explanation I can come up with is that the external code is writing/updating the array. I can now see how this is possible as it completely breaks dataflow, it seems to be undocumented and surely could wreak havoc?!...but it works!
 
To add the confusion/black magic, the code I have inherited which uses this library actually creates a DVR of the Channel A array...surely this would change/upset the ability for the DLL to update the values?!
 
Also, I'm sure if the DLL source/headers are available from PicoScope, I could probably delve inside them to see how/what it is doing - but I see this as more of a 'How on earth is this possible in LV?!' question. In other languages, this is completely trivial - you just pass a pointer to an array and the external code can do what it likes with it, but this is LabVIEW - and things like that aren't generally available when interfacing with external code (within LabVIEW, you would use a DVR).
 
So, questions:
1) How/when is it possible for an external code module to update arrays which are held/maintained in LV asynchronously?
2) What are the conditions where this works/breaks? What happens if you branch the wire? I've already seen that you can put the array into a DVR and it still gets updated correctly.
3) Why couldn't I find any documentation on this behaviour? I tried searching the LV documentation and forums but didn't turn up much.
 
Thanks for any input!

 


LabVIEW Champion, CLA, CLED, CTD
(blog)
Message 1 of 17
(4,277 Views)

If the code is using LV Memory Manager functions to maintain control of the memory which is used internally for buffering, i find this is absolutely possible.

Remember the famous sentence" The Wire is the Variable". Hence the tunnel is a representation of the memory behind it. If the DLL holds the memory, it could update values anytime with a parallel running thread.

 

The most critical thing is that this, in essence, exposes a race condition. So how is it made sure that values are updated orderly?

Norbert
----------------------------------------------------------------------------------------------------
CEO: What exactly is stopping us from doing this?
Expert: Geometry
Marketing Manager: Just ignore it.
0 Kudos
Message 2 of 17
(4,266 Views)

Hi Norbert, thanks for your input, but I don't think it really helps me that much to understand how external code (which could potentially be of unknown/dubious quality) can update the values on a wire asynchronously.

 

My understanding was that external code cannot access LV memory (you have to use the CLFN...). The wrapper they provide doesn't seem to be LabVIEW specific (it's for any language that doesn't support pointers/references) so I highly doubt it is calling any LV specific memory manager functions.

 

You are right though, there is potential for a race condition, but as this is manufacturer supplied code for device interfacing, there isn't much I can do about it (and it has seemingly worked for the client for a long time without issue).

 

The reason for my long post was to try and get an understanding of how this can be possible.


LabVIEW Champion, CLA, CLED, CTD
(blog)
0 Kudos
Message 3 of 17
(4,173 Views)

Hi Sam,

I don't think this should work.  It is a horrible race condition waiting to happen, does the data on the wire get updated at the tunnel or the indicator? or somewhere in between?  I cannot find any documentation from NI suggesting that it is possible to access memory internal to LabVIEW.  I wonder if the author of the DLL made the driver in this way with little LabVIEW knowledge and assumed it would work, and it did. Or, did they know exactly what they were doing, in which case why would they do this?  I imagine that if the wire was branched, LabVIEW would make a copy of the data and a new handle would be created.  It might be that simply adding a branch outside the loop would break the code.  It would be great to get some feedback from NI on this one, Is it a bug/known issue or a little known feature?  I hope you get to the bottom of it!

Michael.

0 Kudos
Message 4 of 17
(4,135 Views)

I saw something like that many moons ago and it blew my mind when I saw the data flow backwards through a wire.

 

A strategic "always copy" node may eliminate that situation.

 

Only people like Rolf can speak with assurance but I believe the create array is allocating a buffer that is used by the dll. Since the rest of your code does not modify the data LV works "in-place" in the buffer used by the dll. Show buffer allocations may confirm some of the guesses.

 

Ben

Retired Senior Automation Systems Architect with Data Science Automation LabVIEW Champion Knight of NI and Prepper LinkedIn Profile YouTube Channel
0 Kudos
Message 5 of 17
(4,129 Views)

Sam_Sharp wrote:

My understanding was that external code cannot access LV memory (you have to use the CLFN...).


Well, as you've shown here, this isn't quite correct. When you pass any data to a DLL by reference (as a pointer or handle), you may be passing a pointer directly to the LabVIEW data. LabVIEW doesn't make any guarantees about when it will do that versus when it will pass a copy - it could change between versions, platforms, even depending on what type of data is passed - so you should always assume it's a copy, but clearly the authors of this driver didn't treat it that way. There's no reason for LabVIEW to make a copy when it doesn't need to do so, but at the same time it's definitely a bad idea for the DLL to hang onto the pointer past the end of the call, because LabVIEW could move and resize the array or release that memory at any time. That said, in the example code you show, there's no reason for LabVIEW to reallocate the array - it comes into the tunnel and never gets modified within the LabVIEW code, so LabVIEW doesn't need to touch it.

 

It's not good programming practice, but I'm not surprised it works. Another discussion of the same thing: http://forums.ni.com/t5/LabVIEW/How-do-I-get-the-memory-address-of-an-array/td-p/1851275

0 Kudos
Message 6 of 17
(4,099 Views)

@Michael_78 wrote:

I imagine that if the wire was branched, LabVIEW would make a copy of the data and a new handle would be created.


An important concept here: LabVIEW never copies data at a wire branch. Data only gets copied at a node, when that node needs to modify the data on the wire and LabVIEW determines that the original data is still needed unchanged. The compiler will try to arrange operations to avoid such copies - for example, if you needed to index out several elements of an array and operate on them individually, in parallel with another operation that modified the array, LabVIEW will generally first index out the elements, then modify the array so that it doesn't need to make a copy of the entire array.

Message 7 of 17
(4,097 Views)

It's a pretty horrible abuse of a few optimization tricks LabVIEW does. And the fact that the DS (dataspace) handles LabVIEW nowadays uses do not get dynamically relocated unless you have somewhere an explicit resize or the array buffer ceases to be needed in the diagram. In the past LabVIEW also used AZ (application zone) handles which could be dynamically relocated at any time, but not for any wire data. Diagram wires for arrays and strings were always DS handles. AZ handles were dropped somewhere around LabVIEW 6 and now only DS handles are used everywhere.

So this works as intended by the programmer but only as long as nobody starts to make modifications to those VIs. An Always Copy in the wire or an explicit resize or even a wire branch that would cause a data copy could throw this completely off.

And the most interesting aspect, some future optimization improvement in a future LabVIEW version could render this code potentially completely useless.

Definitely not the way you want a library to be that gets delivered to customers.

Rolf Kalbermatter
My Blog
Message 8 of 17
(4,080 Views)
Any thoughts/speculation on the fact that in the application code that was written, the array actually gets put into a DVR? I would have expected that putting the array into a DVR would have either completely destroyed the handle, or it would actually serve to protect it?

LabVIEW Champion, CLA, CLED, CTD
(blog)
0 Kudos
Message 9 of 17
(4,065 Views)

A DVR doesn't destroy the data it contains, but rather is a container datatype that protects it. You can think of a DVR a little bit like a reference to the data (or more simplistic a pointer, though that is in many ways misleading from a computer science point of view as a pointer is usually a bare bone low level element without any kind of higher level protection).

You can only access the DVR content through the Inplace structure and as long as the DVR content is referenced inside the Inplace structure, nobody else can access it. So the Inplace structure is implicitly doing an Acquire Semaphore and Release Semaphore on the semaphore each DVR contains too, besides the actual data reference.

However accessing a handle that is inside a DVR from external C code is an exercise that requires very specific C code in the DLL that is only semi-documented through the document and samples for creating your own Nvidia GPU algorithmes for the LabVIEW GPU Analysis Toolkit.

 

The functions that need to be called for this are not all part of the official External Code Reference Manual for LabVIEW.

Rolf Kalbermatter
My Blog
0 Kudos
Message 10 of 17
(4,063 Views)