Search code examples
carrayslabview

LabVIEW Call Library Function yielding array of strings


I need to interface C code to LabVIEW, and my C function needs to give back a two dimensional array of strings. I would rather not be forced to predetermine the size of the array in advance. So I want to know, what is the right data format to use (handle to array of C string pointers? Handle to array of string handles?), how to properly do the allocation, and whether it is better to use an array parameter or a return type. The dialog provided for Call Library Function Node only supports arrays of numeric types, so I'm a little bit lost on how to structure this.


Solution

  • You need the LabVIEW Code Interface Reference Manual to figure this stuff out.

    You are writing a C function that will return a 2D array of strings to LabVIEW. That means you need to be returning LabVIEW's data structure and using LabVIEW's memory allocator. Include "extcode.h" in your C file (ships with LabVIEW). Then create the following C code:

    #include "extcode.h"
    
    struct String2DArrayBlock {
        int32 dimensionSize1;
        int32 dimensionSize2;
        LStrHandle stringArray[1]; // Yes, this is intentional. Do not use LStrHandle* because that syntax changes the memory allocation. Old-school C code. LabVIEW's own C++ code has wrappers for managing this with more type safety. 
    };
    
    typedef String2DArrayBlock** String2DArrayHandle;
    
    MgErr GenerateMyStrings(String2DArrayHandle *ptrToHandle) {
        if (!ptrToHandle)
            return mgArgErr; // Gotta pass a location for us to allocate.
    
        if (*ptrToHandle) {
            // This handle is already allocated. I'm not going to walk you through all the code needed to deallocate.
            return mgArgErr;
        }
    
        const int32 dimSize1 = ComputeHeight(); // This is your function... whereever your data is coming from.
        const int32 dimSize2 = ComputeWidth(); // Same here. 
    
        const int32 numberOfElements = dimSize1 * dimSize2;
    
        if (numberOfElements == 0) {
            return mgNoErr; // Done. NULL means empty array, and the handle is already NULL.
        }
    
        // DSNewHClr allocates the block and flood fills it with all zeros.
        *ptrToHandle = (String2DArrayHandle)DSNewHClr(sizeof(String2DArrayBlock) + ((numberOfElements - 1) * sizeof(LStrHandle))); // -1 because the sizeof block has 1 element. 
        if (!*ptrToHandle)
            return mFullErr; // Out of memory
    
        (**ptrToHandle)->dimensionSize1 = dimSize1;
        (**ptrToHandle)->dimensionSize2 = dimSize2;
    
        LStrHandle *current = (**ptrToHandle)->stringArray;
        for (int32 i = 0; i < numberOfElements; ++i, ++current) {
            std::string myCurrentString = GetMyCurrentString(i); // You write this however you look up the individual strings.
            if (myCurrentString.empty())
                continue; // NULL means empty string
            *current = (LStrHandle)DSNewHClr(sizeof(LStr)); // Allocates a zero-length LStrHandle.
            if (!*current)
                return mFullErr; // The array will be partially filled, but it is in a safe state for early return.
            MgErr err = LStrPrintf(*current, (CStr)"%s", myCurrentString.c_str());
            if (err)
                return err; // The array will be partially filled, but it is in a safe state for early return.
        }
    
        return mgNoErr;
    }
    

    Compile your code against the LabVIEW run-time engine (lvrt.dll).

    In your G code, drop a Call Library Node, add a parameter that is "Adapt to type" and "Pointers to Handles", and wire it with an empty 2D array. And you're done.