LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Allocate memory in C++ .dll for a matrix of structures

Solved!
Go to solution

Hi,

I need to load a C++ piece of code in LabVIEW. Some methods return non trivial data structures whose dimension is not known a priori, so I need to allocate memory for them in the C++ code before returning the structure to LabVIEW.

I was successful in allocating and returning a vector of structures, but I cannot do that for a vector of vectors of structures.

In my working example I have a structure, Measurement_t, which contains a double, an unsigned int and a string, and in order to allocate the an array with several of these structures I do the following:

 

extcode.h:

#define Offset(type, field)		((NI_REINTERPRET_CAST(size_t,(&NI_REINTERPRET_CAST(type*,1)->field)))-1)

#define LStrBuf(sp)	(&((sp))->str[0])				/* pointer to first char of string */
#define LStrLen(sp)	(((sp))->cnt)					/* # of chars in string */
#define LStrSize(sp) (LStrLen(sp)+sizeof(int32))	/* # of bytes including length */

typedef struct {
	int32	cnt;		/* number of bytes that follow */
	uChar	str[1];		/* cnt bytes */
} LStr, *LStrPtr, **LStrHandle;

 

example.h:

#define LVecBuf(sp)	(&((sp))->item[0])                          /* pointer to first item of vector */
#define LVecItem(sp, n)	((&((sp))->item[n]))                    /* pointer to n-th item of vector */
#define LVecLen(sp)	(((sp))->cnt)                               /* # of items in vector */

typedef enum UnitPfx {
    UnitPfxFemto    = 0,    /*!< 10^-15 */
    UnitPfxPico     = 1,    /*!< 10^-12 */
    UnitPfxNano     = 2,    /*!< 10^-9 */
    UnitPfxMicro    = 3,    /*!< 10^-6 */
    UnitPfxMilli    = 4,    /*!< 10^-3 */
    UnitPfxNone     = 5,    /*!< 10^0 = 1 */
    UnitPfxKilo     = 6,    /*!< 10^3 */
    UnitPfxMega     = 7,    /*!< 10^6 */
    UnitPfxGiga     = 8,    /*!< 10^9 */
    UnitPfxTera     = 9,    /*!< 10^12 */
    UnitPfxPeta     = 10,   /*!< 10^15 */
    UnitPfxNum              /*!< Invalid item used only for loop purposes. */
} UnitPfx_t;

typedef struct Measurement {
    double value = 0.0; /*!< Numerical value. */
    UnitPfx_t prefix = UnitPfxNone; /*!< Unit prefix in the range [femto, Peta]. */
    std::string unit = ""; /*!< Unit. \note Can be any string, the library is not aware of real units meaning. */
} Measurement_t

// LabVIEW compatible Measurement_t
typedef struct LVMeasurement {
    double value; /*!< Numerical value. */
    UnitPfx_t prefix; /*!< Unit prefix in the range [femto, Peta]. */
    LStrHandle unit; /*!< Unit. \note Can be any string, the library is not aware of real units meaning. */
} LVMeasurement_t;

// LabVIEW compatible vector of Measurement_t
typedef struct {
    int32 cnt;                            /* number of measurements that follow */
    LVMeasurement_t item[1];              /* cnt measurements */
} LMeas, *LMeasPtr, **LMeasHandle;

 

example.cpp:

// Allocate memory of a LStrHandle and copy the content of an std::string
void string2Output(std::string s, LStrHandle o) {
    MgErr err = NumericArrayResize(uB, 1, (UHandle *)&o, s.length());
    if (!err) {
        MoveBlock(s.c_str(), LStrBuf(* o), s.length());
        LStrLen(* o) = s.length();
    }
}

// Copy the content of a Measurement_t into a LVMeasurement_t
void measurement2Output(Measurement_t m, LVMeasurement_t &o) {
    o.value = m.value;
    o.prefix = m.prefix;
    string2Output(m.unit, o.unit);
}

// Allocate memory of a LMeasHandle and copy the content of an std::vector <Measurement_t>
void vectorMeasurement2Output(std::vector <Measurement_t> v, LMeasHandle * o) {
    int offset = 0;
    MgErr err = DSSetHSzClr(* o, Offset(LMeas, item)+sizeof(LVMeasurement_t)*v.size());
    if (!err) {
        for (auto m : v) {
            LVMeasurement_t * meas = LVecItem(** o, offset);
            measurement2Output(m, * meas);
            offset++;
        }
        LVecLen(** o) = v.size();
    }
}

 

So, using vectorMeasurement2Output I can safely allocate the memory for a vector of measurements and return it to LabVIEW.

Below is the additional code that fails to allocate the memory (or maybe it fails to copy the data, it's kind of hard to debug this stuff, everything just crashes and closes) for a vector of vectors of measurements (note that all the vectors are the same size, so the data can be imagined as a matrix in the end):

 

example.h

#define LMatS1(sp) (((sp))->cnt[0])                             /* # of rows in matrix */
#define LMatS2(sp) (((sp))->cnt[1])                             /* # of cols in matrix */

typedef struct {
    int32 cnt[2];                         /* size of matrix of measurements that follow */
    LVMeasurement_t item[1];              /* cnt vector of measurements */
} LVecMeas, *LVecMeasPtr, **LVecMeasHandle;

 

example.cpp

void matrixMeasurement2Output(std::vector <std::vector <Measurement_t>> v2, LVecMeasHandle * o) {
    int offset = 0;
    MgErr err = DSSetHSzClr(* o, Offset(LVecMeas, item)+sizeof(LVMeasurement_t)*v2.size()*v2[0].size());
    if (!err) {
        for (auto v : v2) {
            for (auto m : v) {
                LVMeasurement_t * meas = LVecItem(** o, offset);
                measurement2Output(m, * meas);
                offset++;
            }
        }
        LMatS1(** o) = v2.size();
        LMatS2(** o) = v2[0].size();
    }
}

 

As you can see I tried allocating the whole memory in a single shot rather than allocating all the vectors separately, but I tried that as well and it didn't work either.

If anyone can suggest how to do this other than pre-allocating a huge matrix directly in LabVIEW that can contain the data of any structure the code will possibly spit out, I'd be greatly thankful.

 

All the best,

 

Filippo

0 Kudos
Message 1 of 14
(2,858 Views)

I'm guessing here, i don't work much with DLLs, but in order to not have to allocate a huge matrix (to be sure), you can make it a 2 step process. The 1st function creates your result matrix, but only returns the size. You use this to allocate a fitting matrix and then you implement a Grab/copy result function that copies/grabs the result to your matrix.

G# - Award winning reference based OOP for LV, for free! - Qestit VIPM GitHub

Qestit Systems
Certified-LabVIEW-Developer
0 Kudos
Message 2 of 14
(2,789 Views)

Hi, thanks for the suggestion!

I guess this could work, I'll give it a try.

Anyway, if anybody knows how to do the allocation thing correctly please let me know, because it would be preferable.

 

Best,

 

Filippo

0 Kudos
Message 3 of 14
(2,785 Views)

(or maybe it fails to copy the data, it's kind of hard to debug this stuff, everything just crashes and closes)

Do you already use an IDE like Visual Studio to debug your code, when called via LabVIEW?

 

There exists a 'Using External code' documentation. https://forums.ni.com/t5/LabVIEW/Latest-version-of-the-Using-External-Code-in-LabVIEW-Manual/td-p/37... 

 

For the functions like NumericArrayResize someone on github created an inofficial documentation which I dont find right now. Unfortunately the official doc doesn't dicuss the function parameters.

 

 

0 Kudos
Message 4 of 14
(2,774 Views)

First improvement would be this:

 

#include "lv_prolog.h"
// LabVIEW compatible Measurement_t
typedef struct LVMeasurement {
    double value; /*!< Numerical value. */
    UnitPfx_t prefix; /*!< Unit prefix in the range [femto, Peta]. */
    LStrHandle unit; /*!< Unit. \note Can be any string, the library is not aware of real units meaning. */
} LVMeasurement_t;

// LabVIEW compatible vector of Measurement_t
typedef struct {
    int32 cnt;                            /* number of measurements that follow */
    LVMeasurement_t item[1];              /* cnt measurements */
} LMeas, *LMeasPtr, **LMeasHandle;

typedef struct {
    int32 cnt[2];                         /* size of matrix of measurements that follow */
    LVMeasurement_t item[1];              /* cnt vector of measurements */
} LVecMeas, *LVecMeasPtr, **LVecMeasHandle;
#include "lv_epilog.h"

 

These includes around data structures that you pass from LabVIEW to your DLL, make sure that the structures use the correct alignment. This is Byte Packing for 32-bit Windows and 8-byte default alignment for all other platforms.

 

Next improvement:

 

// Allocate memory of a LStrHandle and copy the content of an std::string
void string2Output(std::string s, LStrHandle *o) {
    MgErr err = NumericArrayResize(uB, 1, (UHandle *)o, s.length());
    if (!err) {
        MoveBlock(s.c_str(), LStrBufH(*o), s.length());
        LStrLenH(*o) = s.length();
    }
}

 

If the incoming handle is a NULL value (this is a valid value for empty arrays/strings in LabVIEW) then NumericArrayResize() will allocate a new handle and if the handle parameter is by value this memory allocation will get lost as it can't get passed out.

 

Of course a vector of vectors could theoretically be have different size of inner vectors! A LabVIEW 2 D array can't, as it is a consecutive area of memory.

 

Rolf Kalbermatter
My Blog
Message 5 of 14
(2,704 Views)

Hi, thanks for the reply!

 

Do you already use an IDE like Visual Studio to debug your code, when called via LabVIEW?


 Actually no, I don't know how to that, most importantly because I just write the code for the .dll and then send it to a LabVIEW developer who works in another company. I'll ask him if he can do that.

 

Do you already use an IDE like Visual Studio to debug your code, when called via LabVIEW

There exists a 'Using External code' documentation. https://forums.ni.com/t5/LabVIEW/Latest-version-of-the-Using-External-Code-in-LabVIEW-Manual/td-p/37... 


I've checked the link, but which link in there should I go to? The 2003 document link is broken.

 

Best,

Filippo

0 Kudos
Message 6 of 14
(2,642 Views)

Thanks for your suggestions!

I'm already including the lv_prolog.h and lv_epilog.h, I forgot to add it as a relevant part in my minimal code.

I'll try changing the string2Output as you suggest, maybe the strings are not NULL in my working example with the vector, but they are NULL in the other case with the vector of vectors.

Even if vectors in a c++ vector can have different sizes this is not the case for me, I have to return matrices basically.

So apart from the unhandled NULL string case, do you think the code should be working?

 

Best,

Filippo

0 Kudos
Message 7 of 14
(2,640 Views)

@fcona wrote:

 

I'll try changing the string2Output as you suggest, maybe the strings are not NULL in my working example with the vector, but they are NULL in the other case with the vector of vectors.

Even if vectors in a c++ vector can have different sizes this is not the case for me, I have to return matrices basically.

So apart from the unhandled NULL string case, do you think the code should be working?

 


They will be for sure NULL since you allocate the according handle with DSSetHSzClr(). This function makes sure the whole content is initialized to 0. And you better do that in this case, a random value where a handle is expected would make the NumericArrayResize() function believe that it is a valid handle that needs to be resized, and that would for sure end in the General Protection nirvana, rather sooner than later.

Rolf Kalbermatter
My Blog
0 Kudos
Message 8 of 14
(2,626 Views)

Hi,

I tried to make the modification you suggested and actually it improved the behavior of some functions that return strings or structures/vectors that contain strings, because now the values are correctly returned even if the string/structure/vector passed by LabVIEW is empty.

However, the issue with the matrix allocation persists, I still get an error from the memory manager and LabVIEW crashes.

We're trying to figure out how to debug with Visual Studio, but I'm afraid this might take a while, because we're not familiar with this procedure, so we'll have to understand how to set up the debugger.

 

Best,

Filippo

0 Kudos
Message 9 of 14
(2,603 Views)

Well

 

void vectorMeasurement2Output(std::vector <Measurement_t> v, LMeasHandle * o) {
    int offset = 0;
    MgErr err = DSSetHSzClr(* o, Offset(LMeas, item)+sizeof(LVMeasurement_t)*v.size());

 

You pass in the handle by reference. In this case LabVIEW will pass in a NULL handle if you connect an empty array (or) none. DSSetHSzClr() tries to resize that NULL handle and bam!

 

You need to explicitly check if the passed in handle is NULL and if so, you need to call DSNewHClr() instead to allocate a new valid handle. If it is not NULL you can resize it!

Rolf Kalbermatter
My Blog
Message 10 of 14
(2,583 Views)