12-18-2024 02:22 AM - edited 12-18-2024 02:31 AM
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.
12-18-2024 03:15 AM - edited 12-18-2024 03:17 AM
@Steffen01 wrote:
now its just this one left
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:
+1 is because null termination
String parameter configured as shown below:
And now even if you will allocate less chars than your message:
Then it will work without crash:
Now few notes about this:
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:
If you will click on Memory Layout, then you will see it why 40:
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:
And this is why 32:
(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:
And even more advanced exercise is to pass whole LabVIEW Error cluster as parameter.
Then you will pass it as shown below:
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:
Hope this explanation will help.
Just one more thing — when you inserting code samples or headers, then please use this:
(and don't forget to select language like C C++ or Python), then your message will look much better from readability point of view.
12-18-2024 03:27 AM
@Andrey_Dmitriev wrote:
If you will click on Memory Layout, then you will see it why 40:
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.
12-18-2024 02:53 PM
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';
}
12-18-2024 04:14 PM
@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!
12-18-2024 04:40 PM - edited 12-18-2024 04:43 PM
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;
}
12-18-2024 07:20 PM - edited 12-18-2024 07:59 PM
essentially the error message is defined as 512 long
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
I even initialised a string
I attached the header which is used to communicate to the supplier dll
12-18-2024 08:33 PM
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
12-19-2024 02:43 AM - edited 12-19-2024 02:44 AM
@Steffen01 wrote:
essentially the error message is defined as 512 long
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:
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:
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:
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:
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.
12-19-2024 03:08 AM - edited 12-19-2024 03:11 AM
@Steffen01 wrote:
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.