LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Eliminating the copy when passing array in persistent buffer from C to LabVIEW

Solved!
Go to solution
Highlighted

I am using a myRIO to interface between a kinect sensor and some labview code. First I cross compiled the driver (libfreenect) and a small wrapper, and it runs very nicely with a test app written in c. Now I want to make use of labview to process the data. The wrapper is swapping a back/front buffer pair to allow the processing thread to run while newer frames are (over)written into the back buffer.  My problem is getting direct access to those buffers in labview.

 

If I allocate them in labview and then pass as pointers to call library function node, it causes a segfault as soon as the library call leaves (the c code continues to write to the buffers from another thread after labview has deallocated them). Reading around the forums, I have found the most common solution involves allocating in c and then using MoveBlock to 'deference' the array. This causes an extra copy and so isn't a good solution for me.

 

 

I would like to create a labview array handle in c and write to the underlying data, then return this handle from call library node. How can I do this? I can't find a header that declares the array types in c. ILVDataInterface.h seems closest to what I need. Any suggestions?

0 Kudos
Message 1 of 6
(564 Views)

I have found multiple references to the actual implementation, including this one from official docs. However, I can't find a definition within the cintools folder. I will make use of this github library's definitions for now.

0 Kudos
Message 2 of 6
(562 Views)

There is really no good way to avoid a copy of the buffer. LabVIEW handles are only guaranteed to be valid for the duration of the Call Library Node call. After that LabVIEW reserves the right to resize, move and deallocate a handle at any time, for any reasons. So passing in a LabVIEW handle into the C function to let it fill the data into it after the C function has returned is a BIG NO GO!

 

On the other hand when you allocate a buffer in C or in LabVIEW explicitly using for instance a call to DSNewPtr() then you are going to manage the lifetime of that buffer completely at your own will, (and also need to, as LabVIEW will not touch this pointer in any way itself). There is however no fool proof way to let LabVIEW use this pointer directly either so at some point you will need to copy its content from the pointer into a LabVIEW data handle to use it in LabVIEW.

 

This has to do with the fact that LabVIEW is in fact a fully managed environment in itself where allocation of memory buffers need to follow a specific protocol. LabVIEW needs this in order to make certain assumptions about data buffers when compiling your VI into the actual code. Without these assumptions LabVIEW would need to ALWAYS copy every data at every point and the performance of LabVIEW would be terrible that way. LabVIEW's assumptions are all based on data flow rules. Data is generated somewhere and flows through the entire program and whoever receives the data is guaranteed to own it and allowed to modify it UNTIL it hands control back and the data flows to the next node. Your Call Library Node hands control of all data back to LabVIEW the moment it returns from its call. Once the wire ends, the data buffer is marked to be deleted although doesn't necessarily get deleted at that point as LabVIEW maintains buffers for reuse in the next iteration of a loop for instance. But that is all at LabVIEW's discretion and not something you can rely on in any way. This buffer may be needed for some other operation and reused for that, instead of keeping it allocated for reuse in the same code location later.

 

Trying to work around this is very tricky and ALWAYS results in a library that can only be used in very specific, and for a LabVIEW programmer rather non-intuitive, way. This makes use of such a library VERY error prone and even small seemingly harmless changes to the diagram can suddenly break the conditions that allow the library to work in such a way, which will result in rather nasty to debug errors and crashes. 

Rolf Kalbermatter
Averna BV
LabVIEW ArchitectLabVIEW ChampionLabVIEW Instructor
Message 3 of 6
(519 Views)

Thanks for clearing up a lot of the details and pitfalls. I suspected this wouldn't be straightforward due to LabVIEW's memory management.

 

I can see the problem with passing a temporary handle into the function (that was crashing my code) but using DSNewPtr() and similar is not out of the question for me. This part of the library can be hackish if that is the only way to get rid of the copy (many 10's of MB/s, and not the only time the data is copied). The github library I mentioned uses these to allocate arrays, but obviously needs to make assumptions about the array layout in memory. This seems like a no go to, but I also found the same definitions here (http://www.ni.com/example/26288/en/). So is it technically possible to manually allocate the handle from c and then pass it to LabVIEW?

 

LabVIEW will assume it has access to the buffer. My approach is to return a handle to the front buffer, and lock a mutex to stop the c code swapping buffers. Then when the vi's are finished with the front buffer, unlock_mutex is called, allowing the buffers to be swapped again. I can use a sequence structure to force the other vi's to finish touching the data before unlocking the mutex.

 

Perhaps this is more dangerous than I am assuming? To be clear: all allocation and data writing in c, no copy, the vi doesn't have to deallocate anything but does need to release a lock after handling the data.

 

PS I have already been unsuccessful convincing LabVIEW not to immediately delete arrays (using loops, shift registers, etc) and won't be trying that again. It seems to avoid keeping them whenever it can.

0 Kudos
Message 4 of 6
(495 Views)

You can let the dll allocate memory, and then pass it's handle\pointer to LabVIEW. The data won't be copied, just the handle\pointer. The problem is that as soon as you want to do anything from LabVIEW, the data will need to be copied anyway. But this might still be useful, for instance if you don't need all the data, or if you can scan the data part by part to prevent large memory allocation, at the expense of execution speed. This can be made to be pretty save.

 

Of course, you might consider doing the analysis in C as well, and pass a summery to LabVIEW (optionally in a new function). That would keep all the memory stuff in the dll, and only minor amounts of data would be passed to LabVIEW.

0 Kudos
Message 5 of 6
(475 Views)
Solution
Accepted by topic author a1650078
07-14-2018 10:06 PM

First, the LabVIEW data handle format is not secret, but since you have lots of variations in this with arrays of clusters of arrays of clusters of about every possible LabVIEW datatype, it is not really feasable to try to document it beyond the basic principle. 

 

Basically an array is always a handle (and strings are also arrays, just one with a special meaning). And array handles in LabVIEW follow always the same principle. They are a pointer to a memory pointer that contains one int32 value per array dimension, followed by the actual array data directly inlined.

 

Rather than trying to craft your own data type definitions in C, what works much easier is to create the datatype in LabVIEW, connect it to a Call Library Node whose parameter has been configured to Adapt to Type and then right click on the Call Library Node and select "Create C Code". This will create a skeleton C file with the correct type definition and an empty function body that does correspond to the Call Library Configuration at the time you selected the "Create C code" menu entry. From here it is easier to to work in your C code (after you have understood the datatype organization).

 

Now as to your problem you can NOT reserve array handles to not be used/changed by LabVIEW beyond the actual call to your C function. But you can allocate a proper handle in your C code and pass it back to LabVIEW at the time your function is called.

 

#include "extcode.h"

// Take care of platform specific alignment in LabVIEW datatypes
#include "lv_prolog.h"
typedef { int32 dimSize;
double elm[1]; } DoubleArrayRec, *DoubleArrPtr, **DoubleArrHdl;
// Reset alignment to default
#include "lv_epilog.h"

// The user event used to signal to LabVIEW that new data is available
static LVUserEventref gUserEvent = kNotARefNum;
// The actual array handles to swap out
static DoubleArrHdl gFinArrHdl = NULL;
static DoubleArrHdl gNewArrHdl = NULL;
// Use for Mutex datatype whatever your OS provides for mutexes
static Mutex gNewMutex = some_initialization;
static Mutex gFinMutex = some_initialization;

// This is a Call Library Node which gets a user event refnum passed in.
// The parameter needs to be configured as Adapt to Type and the user
// event needs to be one containing a single boolean.
MgErr InstallUserEvent(LVUserEventRef *callback)
{
gUserEvent = *callback);
return noErr;
}

// Here the parameter is a handle to a 1-dimensional array of double floating points values
// Configure this as Adapt to Type and Pass Pointer to Handle.
// Do NOT check the "Constant" checkbox for this parameter in the Call Library Node
// configuration dialog, as that would tell LabVIEW that it is safe to assume that
// the handle can not change during the call, which would lead it to do possible
// optimizations that might crash LabVIEW, since we DO modify the handle here.

// The incoming array can be a NULL pointer which is the LabVIEW canonical
// value for an empty array, so our code needs to be aware of that and not blindly
// assume that we can directly work on that handle.
MgErr GetLastData(DoubleArrayHdl *arr)
{
MgErr err = noErr;

// Save the incoming handle to be used in the background
LockMutex(gNewMutex);
if (gNewArrHdl)
{
// gNewArrHdl is already a handle so don't just overwrite
// it with an incoming handle as we would leak memory that way
if (*arr)
{
err = DSDisposeHandle(gNewArrHdl);
if (err)
return err;
  gNewArrHdl = *arr;
}
// Incoming array is a NULL handle, just leave the original array as it was
}
else
{
// Original array was NULL, so overwrite it with whatever we go passed in
gNewArrHdl = *arr;
}
ReleaseMutex(gNewMutex);
// return the ready handle instead
LockMutex(gFinMutex);
*arr = gFinArrHdl;
ReleaseMutex(gFinMutex);
return err;
}

// In the the background somewhere a routine needs to create the buffer as a LabVIEW
// data handle and store it into gFinArrHdl as soon as it is ready.
MgErr MyDataGenerationRoutine()
{
DoubleArrHdl arr = NULL;

LockMutex(gNewMutex);
arr = gNewArrHdl;
gNewArrHdl = NULL;
ReleaseMutex(gNewMutex);

 int32 size = GetNecessarySize();
// Resize/allocate the handle to be of correct length for the data we want to fill in
// NumericArrayResize() will properly resize an existing handle, or allocate a new
// handle if the incoming handle is a NULL handle
MgErr err = NumericArrayResize(fD, 1, (UHandle*)&arr, size);
if (err)
return err;
err = DoSomeDataFillIn((*arr)->elm, size);
if (err)
return err;
(*arr)->dimSize = size;

LockMutex(gFinMutex);
if (gFinArrHdl)
{
err = DSDisposeHandle(gFinArrHdl);
}
gFinArrHdl = arr;
ReleaseMutex(gFinMutex);

// Signal to LabVIEW that new data is available. Watch out that if this loop is executed
// more often than what your LabVIEW process can handle the incoming user events, the user
// event queue will eventually fill up and crash your LabVIEW process fatally because of
// out of memory error. Especially RT systems are susceptible to that as they often have
// less physical memory than a normal desktop system.
 LVBoolean data = LVBooleanTrue;
return LVPostUserEvent(gUserEvent, &data);
}

 

Rolf Kalbermatter
Averna BV
LabVIEW ArchitectLabVIEW ChampionLabVIEW Instructor
Message 6 of 6
(466 Views)