LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

interface .dll - can I involve some C++ code

Lots of redundant stuff in there.

 

double *outsArray; // seems completely unused

 

double TC_array[20]; // is unnecessary, you can directly assign the values to toLVarray[x] = ... and forget the memcpy()

 

But you must make sure that you tell LabVIEW that it needs to allocate that array of 20 elements before calling the function. Either by explicitly using an Initialize Array and wire the resulting array to the left side of the parameter, or by configuring the minimum size. And yes you have to do that also if you use memcpy()!

 

As to your string, if you configured it in the Call Library Node to be at least 128 bytes, then there won't be any problem but you need to do that in LabVIEW. LabVIEW knows nothing about your C function wanting to copy up to 128 bytes into that buffer!

 

But your use of memcpy() for the string is unsafe. errorMSG.c_str() may be MUCH shorter than 128 byte. Currently you read 128 no matter how long that string really is. That may access memory your process has no right to access and could generate an Access Violation exception. It's also wasteful to copy more than necessary. You should rather use a function like strcpy_s() for copying a C string. That will stop copying on the terminating NULL character and it will also make sure to 0 terminate the resulting string and with the _s postfix is supposed to not go beyond the maximum length of the output string that you specify as one of the parameters. You still have to configure in LabVIEW to allocate a long enough string before calling the underlaying function, so that your C function has some memory space to copy its information into.

 

Rolf Kalbermatter
My Blog
0 Kudos
Message 61 of 73
(294 Views)

@Steffen01 wrote:

 

now its just this one left

 

Steffen01_0-1734494380532.png

 


Yes, this is the problem here - you reading too much, outside of allocated area and this raised an assertion:

 

 

string errorMSG{};
errorMSG = turboCalcError.errorMessage;

int stringlength = 128; // too much
memcpy(toLVerror_string, errorMSG.c_str(), stringlength);

 

 

If you would like to be absolutely safe, then you should share (pass) the size of the allocated space with your wrapper as additional parameter and in case if your message is longer than allocated then trim it (at least).

 

Consider you have a class something like this and given function which will set some values and message:

 

class MyErrorClass {
public:
    bool isError;
    int errorCode;
    std::string errorMessage;
};

void setErrorValues(MyErrorClass& error) {
    error.isError = true;
    error.errorCode = 404;
    error.errorMessage = "Not Found"; // 9 chars
}

 

 

Now your wrapper may looks like this, I would like to recommend to use strncpy_s() here instead of memcpy:

 

 

// This is a very simple example
CLASSTEST_API void fnVerySimple(char *LV_Message, int MaxLen)
{
    MyErrorClass myError;
    setErrorValues(myError);
    size_t strLen = myError.errorMessage.size();
    strLen = (strLen > MaxLen) ? MaxLen : strLen ;

    strncpy_s(LV_Message, strLen + 1, myError.errorMessage.c_str(), strLen);
}

 

 

For strncpy_s refer to cppreference documentation.

 

Now from LabVIEW you will call it like this:

 

snip1.png

+1 is because null termination

String parameter configured as shown below:

Screenshot 2024-12-18 09.46.36.png

And now even if you will allocate less chars than your message:

Screenshot 2024-12-18 09.47.52.png

Then it will work without crash:

Screenshot 2024-12-18 09.48.47.png

 

Now few notes about this:

Screenshot 2024-12-18 09.50.22.png

 

It is not the size of the string, don't confuse, this is the size of the class instead.

 

For example, the class shown above will give you 40 bytes:

Screenshot 2024-12-18 09.53.14.png

 

If you will click on Memory Layout, then you will see it why 40:

 

Screenshot 2024-12-18 09.54.29.png

Take a note, that this layout depends on bitness, the screenshot above is true for 64-bit, but for example for 32-bit it will give 32 bytes:

Screenshot 2024-12-18 09.56.06.png

And this is why 32:

Screenshot 2024-12-18 09.57.01.png

(take a note about alignment gaps between boolean and integer).

The question which you might ask - what the hell such large allocation for the std::string?

It is because For short strings (how short it depends on the implementation), the string data is stored directly within the std::string object itself, avoiding heap allocation, this technique called Small String Optimization. For longer strings, a pointer to heap-allocated memory is used, as usually. Details of the implementation could be differ for MSVC STL, GCC libstdc++ and LLVM, but this is out of the scope in our discussion. 

 

What I really to recommend (once you will get simple wrapper running) is to resize LabVIEW string inside of your wrapper DLL. It is not very complicated, slightly advanced and looks like this:

 

CLASSTEST_API MgErr fnAdvanced(LStrHandle LV_ErrorString)
{
    MyErrorClass myError;
    setErrorValues(myError);
    size_t strLen = myError.errorMessage.size();

    MgErr err = NumericArrayResize(uB, 1, (UHandle*)&LV_ErrorString, strLen);
    if (!err) {
        myError.errorMessage.copy((char*)(*LV_ErrorString)->str, strLen);
        (*LV_ErrorString)->cnt = strLen;
    }
    return err;
}

 

(you have to include extcode.h from cintools from LabVIEW folder  as well as link together with labview.lib)

Then you will call your wrapper something like this and pass String Handle instead of C String Pointer:

Screenshot 2024-12-18 10.04.50.png

 

And even more advanced exercise is to pass whole LabVIEW Error cluster as parameter.

Then you will pass it as shown below:

Screenshot 2024-12-18 10.04.50.png

The cluster will be declared like this:

 

#include "cintools\lv_prolog.h"

typedef struct {
    LVBoolean status;
    int32_t code;
    LStrHandle source;
} TD1;

#include "cintools\lv_epilog.h"

 

(prolog/epolog will take care of alignment for 32/64 bits - in general the same as was shown above for the class)

 And then your wrapper will be:

 

CLASSTEST_API MgErr fnMoreAdvanced(TD1* LV_ErrorCluster)
{
    MyErrorClass myError;
    setErrorValues(myError);
    size_t strLen = myError.errorMessage.size();

    MgErr err = NumericArrayResize(uB, 1, (UHandle*)&(LV_ErrorCluster->source), strLen);
    if (!err) {
        myError.errorMessage.copy((char*)(*(LV_ErrorCluster->source))->str, strLen);
        (*(LV_ErrorCluster->source))->cnt = strLen;
    }

    LV_ErrorCluster->code = myError.errorCode;
    LV_ErrorCluster->status = myError.isError;

    return err;
}

 

All these ways will give you the same results:

Screenshot 2024-12-18 10.10.45.png

Hope this explanation will help.

 

Just one more thing — when you inserting code samples or headers, then please use this:

Screenshot 2024-12-18 10.12.47.png

(and don't forget to select language like C C++ or Python), then your message will look much better from readability point of view.

Message 62 of 73
(285 Views)

@Andrey_Dmitriev wrote:

If you will click on Memory Layout, then you will see it why 40:

 

Screenshot 2024-12-18 09.54.29.png

Take a note, that this layout depends on bitness, the screenshot above is true for 64-bit, but for example for 32-bit it will give 32 bytes:


It also depends on the used C compiler, project selected default alignment rules and, and, and.... Basically C++ object pointers are not a safe thing to pass between components that are not compiled with the same compiler (including version) and the same project settings.

Rolf Kalbermatter
My Blog
0 Kudos
Message 63 of 73
(279 Views)

reg memcopy for the array, that was the only option where the values actually appear. Otherwise its either zeros, or the memory address. Only got 1 more day until I leave the company, so I better try to get the error string sorted

 

 

// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <iostream>
#include "stringheader.h"
#include <array>\

using namespace std;

double arrglob[5] = { 1.1,2.2,3.3,4.4,5.5 };

void foo(char* fpar1, double fpar2, double* farr3[5])
{
double arr3[5] = { 1.1,2.2,3.3,4.4,5.5 };
}

void boo(char* bopar1, double bopar2, double* boarr3[5])
{
double arr4[5] = { 1.1,2.2,3.3,4.4,5.5 }; //works
*boarr3 = arr4;
}

void bar(char* bpar1, double bpar2, double barr3[5])
{
double arr5[5] = { 1.1,2.2,3.3,4.4,5.5 };
barr3 = arr5;
}

void goo(double gpar1, double gpar2, double* garr[5])
{
*garr = arrglob;
}

void moo(char* mpar1, double mpar2, double marr[5])
{
marr = arrglob; //works
}

void zoo(string zpar1, double zpar2, double* zpar3)
{
*zpar3 = 5;
}

void roo(char* rpar1, double rpar2, char* rpar3, double rarr3[5])

{
double arr3[5] = { 1.1,2.2,3.3,4.4,5.5 }; //works
memcpy(rarr3, arr3, 5 * sizeof(double)); //with this the array comes through, no need for pointer here

int stringlength = 128;
string errorMSG = "no error, nice.";
memcpy(rpar3, errorMSG.c_str(), stringlength);
rpar3[stringlength - 1] = '\0';
}

void TC_inputsA(char* fromLVcomp, char* fromLVrefrigerant, char* fromLVcalcType, double fromLVkW, double fromLV_SDT, double fromLV_SST, double fromLV_SSH, double fromLV_SC, double fromLV_App, int fromLVecon, char* LVresults)
{
*LVresults = 5;
}

void TC_inputsS(char* fromLVcomp, char* fromLVrefrigerant, char* fromLVcalcType, double fromLVkW, double fromLV_SDT, double fromLV_SST, double fromLV_SSH, double fromLV_SC, double fromLV_App, int fromLVecon, double LVresultsArr[5], char* toLVerror_string)

{
double ArrLV[5] = {1.1,2.2,3.3,4.4,5.5};
memcpy(LVresultsArr, ArrLV, 5 * sizeof(double));

int stringlength1 = 128;
string errorMSG1 = "no error, nice.";
memcpy(toLVerror_string, errorMSG1.c_str(), stringlength1);
toLVerror_string[stringlength1 - 1] = '\0';
}

0 Kudos
Message 64 of 73
(259 Views)

@Steffen01 wrote:

reg memcopy for the array, that was the only option where the values actually appear. Otherwise its either zeros, or the memory address.


Did I say anywhere anything about memcpy() being wrong for the array? I specifically talked about the errorString!

Rolf Kalbermatter
My Blog
0 Kudos
Message 65 of 73
(255 Views)

Steffen01_0-1734561262885.png

maybe I got that wrong then

 

the error string is a *char pointer, strncopy wants a constant, need to sort this out

 

#include <stdio.h>
#include <string.h>

int main() {
char dest[10] = "";
const char* src="Hello, world!";

errno_t err = strncpy_s(dest, sizeof(dest), src, 5);
if (err == 0) {
printf("Copied string: %s\n", dest);
}
else {
printf("Error copying string: %d\n", err);
}

return 0;
}

0 Kudos
Message 66 of 73
(247 Views)

essentially the error message is defined as 512 long

Steffen01_0-1734570179834.png

 

so I did this, was hoping it makes some empty sting of the same size, copys the error message in and shortens it

 

 

 

 

std::string errorMSG(512, '-');
errorMSG = turboCalcError.errorMessage;

size_t strLen = errorMSG.size();
strLen = 127;
strncpy_s(toLVerror_string, strLen+1, turboCalcError.errorMessage, strLen);

 

 

 

 

error message is for sure shortened. Still the same outcome / error. The wrapper dll should deal with all of this and Labview only sees 128 characters

 

Steffen01_1-1734570840859.png

 

I even initialised a string

Steffen01_2-1734571183584.png

I attached the header which is used to communicate to the supplier dll

 

0 Kudos
Message 67 of 73
(234 Views)

Steffen01_0-1734575524576.png

edit expired. here its 32 byte allocated for the error, but its 512? Suppose as soon its populated, it will adapt its size. I have no control over that, it will happen inside the supplier dll

0 Kudos
Message 68 of 73
(227 Views)

@Steffen01 wrote:

essentially the error message is defined as 512 long

Steffen01_0-1734570179834.png

 

so I did this, was hoping it makes some empty sting of the same size, copys the error message in and shortens it

 

std::string errorMSG(512, '-');
errorMSG = turboCalcError.errorMessage;

size_t strLen = errorMSG.size();
strLen = 127;
strncpy_s(toLVerror_string, strLen+1, turboCalcError.errorMessage, strLen);

 

error message is for sure shortened. Still the same outcome / error.


You're on the right way, the only possible problem is that you using std::string where you should use char *, which can be obtained with c_str(). So, it should be

 

strncpy_s(toLVerror_string, strLen+1, turboCalcError.errorMessage.c_str(), strLen);

 

if I understood your code correctly.

In general if you have troubles with strncpy_s (there are some specific points), then using of memcpy() is not prohibited. All what you have in your hands - the only four things — pointers to the source (which is pointer to the memory, where your characters are located) and to destination where it nees to be written, the length of the source string and size of the destination buffer, which should be not exceeded under any circumstances. That is.

If you know that your message is 512 chars long, then allocate 512+1 bytes. Also if you will allocate 1K, then nothing will be principal wrong (but dynamic allocation depends on size is more elegant, of course.

 

Now about this:


@Steffen01 wrote:
Andrey_Dmitriev_0-1734595659033.png

essentially the error message is defined as 512 long


 

You will never ever get here "512", this is size of the class structure (which also returned by sizeof()).

 

Let do simple experiment with class contains two members - int and std::string. I will create instance and dump all addresses and content to understand:

 

 

#include <iostream>
#include <string>
#include <iomanip>

using namespace std;

class MyClass {
public:
    int myInt;
    string myString;

    MyClass(int i, const string& s) : myInt(i), myString(s) {}
};

void printHexDump(const void* addr, size_t len) {
    const unsigned char* pc = static_cast<const unsigned char*>(addr);
    for (size_t i = 0; i < len; i++) {
        cout << setw(2) << setfill('0') << hex << static_cast<int>(pc[i]) << " ";
        if ((i + 1) % 8 == 0) cout << endl;
    }
    cout << dec << endl;
}

int main() {
    MyClass obj(42, "Hello, World!");

    cout << "String: " << obj.myString << endl;

    cout << "\nSize of the class object: " << sizeof(obj) << endl;

    cout << "\nMemory address of the object: " << &obj << endl;
    cout << "Address of myInt: " << &obj.myInt << endl;
    cout << "Address of myString: " << &obj.myString << endl;
    cout << "Address of myString.c_str(): " << static_cast<const void*>(obj.myString.c_str()) << endl;

    cout << "\nOffsets:" << endl;
    cout << "Offset to myInt: " << reinterpret_cast<char*>(&obj.myInt) 
                                 - reinterpret_cast<char*>(&obj) << endl;
    cout << "Offset to myString: " << reinterpret_cast<char*>(&obj.myString) 
                                    - reinterpret_cast<char*>(&obj) << endl;
    cout << "Relative Offset to myString.c_str(): " << hex << reinterpret_cast<char*>(&obj.myString) 
                                        - reinterpret_cast<const void*>(obj.myString.c_str()) << endl;

    cout << "\nHexadecimal dump of the class object:" << endl;
    printHexDump(&obj, sizeof(obj));

    return 0;
}

 

 

Memory layout of this class:

Screenshot 2024-12-19 09.18.37.png

 

Now if I call this for MyClass obj(42, "Hello, world!") (the string is short — 13 chars only):

Now you will see memory layout and addresses, as you can see - the string stored inside of the class:

Screenshot 2024-12-19 09.32.09.png

But the situation gets changed for longer string:

 

    MyClass obj(42, "Welcome, LabVIEW!");

 

Now you see that the address of the string (obtained from c_str() is completely different:

Screenshot 2024-12-19 09.36.11.png

And instead of the string itself this address is stored.

I guess the byte "1f" at the end will tell to the class exactly about this (in previous experiment it was 0f).

This is why important to use c_str(), because starting from length 16 you will get different address (for short strings the address of the std::string is equal to c_str()).

Hope it helps.

0 Kudos
Message 69 of 73
(213 Views)

@Steffen01 wrote:

Steffen01_0-1734575524576.png

edit expired. here its 32 byte allocated for the error, but its 512? Suppose as soon its populated, it will adapt its size. I have no control over that, it will happen inside the supplier dll


Andrey already answered this basically. You are mixing and matching everything together. string is a C++ template library class and as such an object. A C++class is in fact a structure with various data, usually pointers including possibly one or more pointers to an array of function pointers that represents the virtual function table, maybe some reference count and what else. The template class library is a fairly hairy thing if you want to understand how it implements things and uses several C++ advanced features to allow automatic resource handling when declaring value instances that get automatically cleared up when the variable leaves the declaration scope. The size of that class structure has nothing to do with what the class can contain as data. Sometimes it contains no extra data, other times it contains one or more pointers that can point potentially to many Megabytes of data and extra data elements that help the class to know how much data it points to, if that data is owned or borrowed, etc. etc.

What you need to know is that "string" is a class, and the actual internal implementation is implementation specific, meaning your C compiler comes with template headers that define how it looks but these template headers may look different for every C compiler and sometimes even between versions of the same C compiler. string is NOT a char array, but contains one, although it could contain also a wchar array (for Unicode support) or anything else the compiler builder finds useful. There are well known elements such as the c_str() member which returns the const (read-only) internal char pointer to be used for standard C functions, and the length() member returning the actual number of characters the string class contains currently. Other member functions are size() which is an alias for length() and things like clear(), append() etc. It also supports operator overloading such that the + sign is for instance an alias for the append() member function. All nice and good if you program in C++ and it makes working with strings a lot more convenient, but not really applicable if you want to interface to LabVIEW.

Rolf Kalbermatter
My Blog
Message 70 of 73
(205 Views)