LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Returning an array of strings from a DLL

I tried to return an array of strings from a DLL that I called from LabView. 

It took me quite some effort to figure out how this is done as the available documentation is really sparse. In order to save some others the same pain I decided to write a short demo project to show how it can be done. 

Have a look at the attached zip file. It contains a demo VI plus some C++ code for a DLL that will construct and return an array of strings. 

I hope this code is useful for some of you.

0 Kudos
Message 1 of 6
(2,058 Views)

Your example is a start but it has for instance the problem that it does not account for the string array to be not empty (NULL) on entry and simply leaking that memory and all the strings contained in such an array.

 

Far fetched? No not really. The diagram is available to any user of your function and most users will in their uninformed state try just about everything if something doesn't seem to work, including connecting non-empty input arrays to the terminals since there are so many posts on these forums that state that you have to provide all buffers on the calling diagram. They do not know that that only applies to C pointers but not to LabVIEW handles and they are also not able to make that distinction even when they look in the Call Library Node configuration!

Rolf Kalbermatter
My Blog
Message 2 of 6
(2,001 Views)

Fair enough. You are right.

 

My example is a starting point and nothing more. However, it can save someone a lot of time to figure out the basics.

Nevertheless, you are right that it is not production ready and should be improved if used in a real application. Anyway, I tried to keep it as concise as possible in order to show the basic procedures.

 

Nice greetings, Manfred

Message 3 of 6
(1,996 Views)

Thank you for your valuable sharing.
I would like to use this as follows. This is to transfer DB table array type data  to LABVIEW.  It seems to work fine, but.. what potential problems are there when using  this?.    from those who lack understanding of this code.

 

void dll_sqllite(int Procedure, LVArrayOfStringsHdl *arrayOfGenlist, int GenNumber[], char* inputStr)
.....................
SQLite::Database db(filename_user_db3);
  vector<string> GenderList;
  vector<int> GenderCount;
SQLite::Statement query(db, "select gender, count(*) from user group by gender;");
// query fetch.
while (query.executeStep())
{
  GenderList.push_back(query.getColumn(0));
  GenderCount.push_back(query.getColumn(1));
}
// string array to labview
int32_t numStrings = int32_t(GenderList.size());
*arrayOfGenlist = (LVArrayOfStringsHdl)(DSNewHandle(Offset(LVArrayOfStrings, String) + sizeof(LStrHandle) * numStrings));
(**arrayOfGenlist)->dimSize = numStrings;
for (size_t i = 0; i < numStrings; ++i) {
// create individual string handles
(**arrayOfGenlist)->String[i] = (LStrHandle)(DSNewHandle(Offset(LStr, str) + sizeof(uChar)));
(*(**arrayOfGenlist)->String[i])->cnt = 0;
// print into each handle
LStrPrintf((**arrayOfGenlist)->String[i], (CStr)"%s", GenderList[i].c_str());
}
// others...
for (vector<int>::size_type i = 0; i < GenderCount.size(); i++) {
GenNumber[i] = GenderCount[i];
}
.......................
// clear
GenderList.clear();
GenderCount.clear();

0 Kudos
Message 4 of 6
(914 Views)

From my perspective, the code seems to be fine. If you like my code snippet, please give me a Kudo. 

Message 5 of 6
(905 Views)

From my perspective there is quite a bit suboptimal to really almost wrong.

 

First, you simply assume that the incoming arrayOfGenlist parameter is a NULL handle from the LabVIEW diagram. That is only really always true if you explicitly wire an empty string array constant to the left terminal of the Call Library Node. If you leave it open there is a chance that LabVIEW retains the string array handle between subsequent calls for performance reason and your unconditional overwriting of the incoming parameter would each time leak the according array handle and its strings contained in there.

 

Also since the diagram is accessible to the casual and untrained LabVIEW user, there is nothing that prevents them from changing that wired empty string array handle into one that has predefined data in there, and then you leak those again.

 

Second, you pass in the second array as a C array pointer. That is very ugly! For one you do not know how many elements the function will want to fill in, so you either have to pre-allocate a huge array beforehand on the diagram to pass in or you risk that the function eventually will overrun the buffer that you passed in from the diagram (you did pass in a pre-allocated array, didn't you?)

 

The entire API is mixing and matching two completely different paradigms, which only can lead to future confusion by anyone who has to maintain that code as well as any potential users of your VI library.

 

Either make it all C array pointers (which is absolutely unpractical for the string array due to the fact that they all need to be pre-allocated on the diagram but you do not know at that point how much you need) or use LabVIEW array handles throughout (which makes the C code in the DLL significantly more complex but this is a one time effort and will make the function more user friendly on the LabVIEW level).

 

#include "extcode.h"

#if Is64Bit
#define uPtr uQ
#else
#define uPtr uL
#endif

#include "lv_prolog.h"
struct {
    int32_t dimSize;
    LStrHandle String[1];
} **LVArrayOfStringsHdl;

struct {
    int32_t dimSize;
    int32_t elm[1];
} **LVArrayOfIntegers;
#include "lv_eplog.h"


MgErr dll_sqllite(int Procedure, LVArrayOfStringsHdl *arrayOfGenlist, LVArrayOfIntegers *arrayOfGenNumber, char* inputStr)
{
    .....................
    SQLite::Database db(filename_user_db3);
    vector<string> GenderList;
    vector<int> GenderCount;
    SQLite::Statement query(db, "select gender, count(*) from user group by gender;");
    // query fetch.
    while (query.executeStep())
    {
        GenderList.push_back(query.getColumn(0));
        GenderCount.push_back(query.getColumn(1));
    }
    // string array to labview
    int32_t i, numElms = int32_t(GenderList.size());
    MgErr err;

    // if we have an input array that is valid and larger then what we need we should deallocate the superflous elements in it
    if (*arrayOfGenlist)
    {
        for (i = numElms; i < (**arrayOfGenlist)->dimSize; i++)
        {
            DSDisposeHandle((**arrayOfGenlist)->String[i]);
        }
    }
    err = NumericArrayResize(uPtr, 1, (UHandle*)arrayOfGenlist, Offset(LVArrayOfStrings, String) + sizeof(LStrHandle) * numElms);
    (**arrayOfGenlist)->dimSize = numElms;
    for (i = 0; !err && i < numElms; ++i)
    {
        // create individual string handles
        err = NumericArrayResize(uB, 1, (UHandle*)&(**arrayOfGenlist)->String[i], Offset(LStr, str) + GenderList[i].c_str().length());
        MoveBlock(GenderList[i].c_str(), LStrBuf(*((**arrayOfGenlist)->String[i])), GenderList[i].c_str().length()); 
        (*(**arrayOfGenlist)->String[i])->cnt = GenderList[i].c_str().length();
    }
    if (!err)
    {
        // others...
        numElms = int32_t(GenderCount.size());
        err = NumericArrayResize(uPtr, 1, (UHandle*)arrayOfGenNumber, Offset(LVArrayOfIntegers, elm) + sizeof(int32_t) * numElms);
        for (vector<int>::size_type i = 0; i < numElms; i++)
        {
            (**arrayOfGenNumber)->elm[i] = GenderCount[i];
        }
        (**arrayOfGenNumber)->dimSize = numElms;
        .......................
    }
    // clear
    GenderList.clear();
    GenderCount.clear();
    return err;
}
Rolf Kalbermatter
My Blog
Message 6 of 6
(879 Views)