From 11:00 PM CDT Friday, Nov 8 - 2:30 PM CDT Saturday, Nov 9, ni.com will undergo system upgrades that may result in temporary service interruption.
We appreciate your patience as we improve our online experience.
From 11:00 PM CDT Friday, Nov 8 - 2:30 PM CDT Saturday, Nov 9, ni.com will undergo system upgrades that may result in temporary service interruption.
We appreciate your patience as we improve our online experience.
01-25-2024 05:27 AM
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
Solved! Go to Solution.
01-26-2024 07:04 AM
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.
01-26-2024 07:16 AM
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
01-26-2024 10:06 AM - edited 01-26-2024 11:05 AM
(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.
01-28-2024 01:49 PM - edited 01-28-2024 01:51 PM
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.
01-30-2024 05:30 AM
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
01-30-2024 05:35 AM
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
01-30-2024 06:29 AM
@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.
01-31-2024 04:35 AM
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
01-31-2024 12:38 PM
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!