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: 

Shrinking LStrHandle Size in DLL

Solved!
Go to solution

Hi!

 

I'm working on a DLL that will be called from LabVIEW (currently LV 2014 32-bit, but we may move to LV 2018 64-bit soon on Windows 10) in which I have functions that take pointers to LStrHandle types and arrays.  I have code that will either allocate a new handle if the handle is NULL or resize the handle if it is not large enough to hold the data I need to store into it.  This appears to be working as I expected as I step through it in the Visual Studio debugger.

 

However, what if I don't use up all of the space I allocated for the string?  Usually, this comes up in C because a function will read up to a certain number of bytes into a buffer and return the number of bytes actually read.  Suppose I have a function that reads data from a device into a LStrHandle *, like so:

 

EXTERNC BLAHBLAH_API MgErr ReadExample(uint32_t readerHandle, LStrHandle *dest, int32_t to_read)
{
    MgErr lverr = mgNoErr;

    if(dest)
    {
        // A LabVIEW string is a size followed by the string data.
        size_t total_dest_size = to_read + offsetof(LStr, str);

        if(*dest)
        {
            // Resize string only if it's not already big enough.
            if(DSGetHandleSize(*dest) < total_dest_size)
                lverr = DSSetHandleSize(*dest, total_dest_size);
        }
        else
        {
            // LabVIEW can use a NULL handle to mean an empty string, so we have to allocate a new
            // handle.
            *dest = (LStrHandle)DSNewHandle(total_dest_size);
        }

        // Check again in case we failed to allocate a new handle above.
        if(*dest)
        {
            // This will read AT MOST 'cnt' bytes into the string and return the bytes actually read.
            to_read = DoTheRead(readerHandler, (*data)->str, to_read);

            // Is this okay...
            (*data)->cnt = to_read;

            // Or do I also need to do this to shrink the allocation size?
            lverr = DSSetHandleSize(*dest, to_read + offsetof(LStr, str));
        }
        else
        {
            lverr = mFullErr;      // "Memory is full."
        }
    }
    else
    {
        lverr = mgArgErr;
    }

    return lverr;
}

Note that this function is a simplified example that I put together just for this post.

 

As you can see, I can set the string 'cnt' member to indicate the number of bytes actually in the string, but I don't know if I then also need to resize the handle using DSSetHandleSize to shrink the allocated memory.

 

Thanks in advance for any thoughts on this.

0 Kudos
Message 1 of 4
(2,436 Views)
Solution
Accepted by topic author jdeguire

I'm sure you are aware that the C code that you posted won't compile at all due to various syntax errors, so I'm not focusing on that here but the questions you posed and some recommendations.

 

First you could simplify the whole array resizing significantly by using NumericArrayResize(). This will determine if it got a NULL handle and create one in that case or resize the handle otherwise and it will also take care of any alignment issues that might exist (e.g. a 1D array of int64 elements does have a 32 bit value in front indicating the number of array elements it contains, but the first int64 element will be on offset 8 and not 4 when you compile this for anything but 32-bit Windows LabVIEW.

 

Second, your naming of the readerHandle might suggest that the use of an int32 value for this could be not a good one. Is it really a 32-bit value (which is possible but contrary to the naming). Otherwise make this an uintptr_t. Also in that case change this parameter in the LabVIEW CLN to be a pointer sized integer.

 

As to your question, you can of course add a call to DSSetHandleSize() before leaving your function. But LabVIEW will use the LStrLen() value in the string handle to determine the actual string size so it is not strictly necessary. It may be beneficial though to do that if for some reason the initial size that you have allocated for the handle with the to_read input parameter value might differ significantly from the final size that is really filled in. It won't create a memory leak but will of course hold onto that extra space in the handle even if it is not used at all. But for a few bytes of memory I never bother, as any handle resizing is potentially an expensive operation.

 

EXTERNC BLAHBLAH_API MgErr ReadExample(uint32_t readerHandle, LStrHandle *data, int32_t to_read)
{
    MgErr lverr = mgArgErr;

    if (data)
    {
lverr = NumericArrayResize(uB, 1, (UHandle*)data, to_read);
        if (!lverr)
        {
            // This will read AT MOST 'to_read' bytes into the string and return the bytes actually read.
            to_read = DoTheRead(readerHandle, (**data)->str, to_read);
// better would be to use
to_read = DoTheRead(readerHandle, LHStrBuf(*data), to_read);
// but what about error handling in the DoTheRead() function? Could it return
// a negative value in that case? If so you need to catch that here somehow as
// LabVIEW really will joke on such string sizes

            // Is this okay...
            (**data)->cnt = to_read;
// better would be to use following as we can't use the LHStrLen()
// macro here because it is not usable as l-value

// LStrLen(**data) = to_read;

            // Or do I also need to do this to shrink the allocation size?
// NO not really unless the returned string can get significantly smaller than the requested one
            lverr = NumericArrayResize(uB, 1, (UHandle*)data, to_read);
        }
    }
    return lverr;
}

 

Rolf Kalbermatter
My Blog
0 Kudos
Message 2 of 4
(2,404 Views)

Hi Rolf, Thanks for responding! There's a lot here, so I'll reply to each bit below.

 


@rolfk wrote:

I'm sure you are aware that the C code that you posted won't compile at all due to various syntax errors, so I'm not focusing on that here but the questions you posed and some recommendations.


Yup, I figured as much.  I slapped this bit together from code I had just as a simple example for this post.  You are also correct that the read function (actually recv()) can return negative to indicate an error.  I did not show that here because I wanted to keep the example simple, but I do handle that in my code.  I'll also mention that I did change my code to use the LStrLen and LStrBuf macros after reading your response.

 


@rolfk wrote:

First you could simplify the whole array resizing significantly by using NumericArrayResize(). This will determine if it got a NULL handle and create one in that case or resize the handle otherwise and it will also take care of any alignment issues that might exist (e.g. a 1D array of int64 elements does have a 32 bit value in front indicating the number of array elements it contains, but the first int64 element will be on offset 8 and not 4 when you compile this for anything but 32-bit Windows LabVIEW.


Ah, that's a good trick with the NumericArrayResize() for strings.  I'll have to make use of that.  Oddly enough, I don't even use that for allocating arrays because I allocate arrays of clusters (I surround my types with includes for "lv_prolog.h" and "lv_epilog.h" to get the correct alignment, along with using C's offsetof()).

 


@rolfk wrote:

Second, your naming of the readerHandle might suggest that the use of an int32 value for this could be not a good one. Is it really a 32-bit value (which is possible but contrary to the naming). Otherwise make this an uintptr_t. Also in that case change this parameter in the LabVIEW CLN to be a pointer sized integer.


It's actually a socket handle, which you are correct is actually a uintptr_t (well, UINT_PTR on Windows).  I somehow missed that LabVIEW CLNs supported pointer-sized integers, so I was actually passing a uint64_t* to store the socket handle regardless of platform.  I'll change this, thanks!

 


@rolfk wrote:

As to your question, you can of course add a call to DSSetHandleSize() before leaving your function. But LabVIEW will use the LStrLen() value in the string handle to determine the actual string size so it is not strictly necessary. It may be beneficial though to do that if for some reason the initial size that you have allocated for the handle with the to_read input parameter value might differ significantly from the final size that is really filled in. It won't create a memory leak but will of course hold onto that extra space in the handle even if it is not used at all. But for a few bytes of memory I never bother, as any handle resizing is potentially an expensive operation.


That's what I was hoping to hear.  I assumed that the LabVIEW environment would handle strings and arrays as you describe here (like C++ does with std::string or std::vector), but I wasn't sure how how that plays out in external code.  I'm not too concerned with losing a bit of memory for now.

 

One thing I will have to investigate when I get further along is how LabVIEW recycles handles.  That is, if I call my CLN 5 times, will I get 5 completely different handles (or NULL) that I need to allocate and size or will I get the same handle 5 times that I can just refill with new data?  That will probably have a bigger impact on memory usage than leaving my allocations slightly too large.  I won't worry about this just yet, though.

 

Thanks again!

0 Kudos
Message 3 of 4
(2,386 Views)

@jdeguire wrote:

 

 

One thing I will have to investigate when I get further along is how LabVIEW recycles handles.  That is, if I call my CLN 5 times, will I get 5 completely different handles (or NULL) that I need to allocate and size or will I get the same handle 5 times that I can just refill with new data?  That will probably have a bigger impact on memory usage than leaving my allocations slightly too large.  I won't worry about this just yet, though.

 


LabVIEW will usually try to minimize the number of times it has to deallocate and reallocate handles since that is a fairly expensive operation. So if you have a loop and in that loop a function receives a handle that is temporarely used and then the wire stops, LabVIEW may still decide to keep that handle allocated and reuse it in each loop iteration. But if it does that or not you can not know for sure. If you would want to make sure about that you could add a shift register to the loop and put the handle from after the function into that shiftregister and then use it again in the next iteration. In principle LabVIEW might decide to do something that is like a hidden shift register in order to save unnecessary memory deallocation-allocation cycles. Yes the handle might still need to be resized between calls which is often similar to a deallocation-allocation, but in loops it is often so that the size of arrays and handles remains pretty much the same between iterations, so there is a huge saving potential by doing this.

 

When and if it does it is however dependent on complicated optimization algorithms that can even change between LabVIEW versions.

Rolf Kalbermatter
My Blog
0 Kudos
Message 4 of 4
(2,375 Views)