12-16-2024 03:12 AM - edited 12-16-2024 03:13 AM
@rolfk wrote:
Guessing when doing C programming is about as safe as driving car with closed eyes.
Well, healthy guessing is also possible way, usually if I completely lost in the parameters, just create trivial stripped down dll, slightly modify header, and you will see immediately
Using NI CVI, for example, header:
#ifndef __header_test_H__
#define __header_test_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "cvidef.h"
void foo (char* par1, double par2, double* par3[20]);
void bar (string par1, double par2, double* par3[20]); //string is the problem
#ifdef __cplusplus
}
#endif
#endif /* ndef __header_test_H__ */
Result of the import:
foo:
and bar:
And now time to think how to deal with string (the Rolfs' suggestion above is fully correct).
12-16-2024 03:56 AM - edited 12-16-2024 04:29 AM
@Andrey_Dmitriev wrote:
@rolfk wrote:
Guessing when doing C programming is about as safe as driving car with closed eyes.
Well, healthy guessing is also possible way, usually if I completely lost in the parameters, just create trivial stripped down dll, slightly modify header, and you will see immediately
That's called trial and error, not guessing. Still not healthy doing with a car, but in programming it is just extremely time consuming and often rather frustrating. But sometimes it is the only way to proceed when there is no, or even worse, wrong documentation about library functions.
This however is not about no documentation, but about pretty much being unable to see the trees in the forest. I'm all for helping someone get his or her problem solved, and have spend many hours on these forums to do so but sometimes it feels very hopeless. When someone doesn't even understand the basic principles of C programming but insists in trying to use the Call Library Node, it feels like fighting windmills trying to help them.
Basically, if you are in doubt about being able to create a VI with Call Library Node yourself without using the Import Library Wizard, stop and think again: The Call Library Node is not something you should ever use in that case in any way, other than in existing, carefully tested and working VIs.
12-16-2024 02:36 PM
unfortunately I have to pass some text in (strings, chars, whatever).
String length is oversized, keep it at 128. Hard to use that with 10 characters only and not worried about memory etc.
Ok I try with character pointers, still got 4 days until I leave this place.
changing the output from array to string did not help. That only leaves strings. One should hope doubles and ints get through ok. Even have the bool changed to an int,
this is what the wissard says
extern "C" DllExport string TC_inputs(string fromLVcomp, string fromLVrefrigerant, string fromLVcalcType, double fromLVkW, double fromLV_SDT, double fromLV_SST, double fromLV_SSH, double fromLV_SC, double fromLV_App, int fromLVecon, string* LVresults);
this is in the header
extern "C" DllExport string TC_inputs(string fromLVcomp, string fromLVrefrigerant, string fromLVcalcType, double fromLVkW, double fromLV_SDT, double fromLV_SST, double fromLV_SSH, double fromLV_SC, double fromLV_App, int fromLVecon, string* LVresults);
its in the dll as well (dll export viewer)
funny enough, sometimes its in the dll export viewer but not in Dependencies, but here it is
maybe needs to be a string pointer for inputs, I shall try
12-16-2024 02:42 PM
string as C datatype is a C++ Template library class. DO NOT USE THAT AT ALL, if you want to be able to interface to it with the Call Library Node! C++ classes can only be interfaced to by C++ compilers and it's even worse. C++ compiler from Microsoft uses different class layout than C++ compiler from GCC for instance. So it is not even portable between different C++ compilers, lets not talk about other environments than C++ compilers.
12-16-2024 03:04 PM
ok, I try char and let you know how it went. As this is for future readers as well, lots of my code I found here too.
12-16-2024 08:47 PM
this is in Labview
void TC_inputsS(CStr fromLVcomp, CStr fromLVrefrigerant, CStr fromLVcalcType, double fromLVkW, double fromLV_SDT, double fromLV_SST, double fromLV_SSH, double fromLV_SC, double fromLV_App, int32_t fromLVecon, double *LVresultsArr, CStr toLVerror_string);
this is the header
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);
I am not sure why function foo was not recognised but function boo was, its the exact same thing. But at least I know that output array plus error string (2 function outputs) could be working as well instead of cramming everything into one string.
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 arr3[5] = { 1.1,2.2,3.3,4.4,5.5 }; //works
}
12-17-2024 01:33 AM - edited 12-17-2024 01:43 AM
@Steffen01 wrote:
void boo(char* bopar1, double bopar2, double* boarr3[5])
{
double arr3[5] = { 1.1,2.2,3.3,4.4,5.5 }; //works
}
Not sure what you mean with it works. It definitely will not return anything to LabVIEW. The parameter name is called boarr3, the variable you do the initialization for is called arr3. Not the same thing.
And you can't return data to LabVIEW like this. Well you can but only as a pointer sized variable. Then you can play C compiler on the diagram by copying the data from that pointer into a LabVIEW array. Or you could do it on the C side instead:
void boo(char* bopar1, double bopar2, double boarr3[5])
{
double arr3[5] = { 1.1,2.2,3.3,4.4,5.5 }; //works
memcpy(boarr3, arr3, 5 * sizeof(double));
}
And of course make sure to configure that parameter in the LabVIEW Call Library Node to contain at least 5 array elements.
As to why boo works and foo doesn't for you: My guess, you still used an old header with a definition that LabVIEW does not like. The DLL only contains function names, nothing else. The LabVIEW Import Library Wizard can't determine anything about the parameters and their types from the DLL. It uses the header you specify and matches the function names in there with function names it finds in the DLL. And yes even a header file is not really enough to allow the wizard to create really correct VIs. It has to guess in several aspects how the C syntax is meant to be applied and those guesses are only really right if the DLL interface is pretty trivial. As soon as arrays and strings are concerned, things go haywire very fast.
12-17-2024 08:07 PM - edited 12-17-2024 09:03 PM
foo and boo have the same header, so no idea why the wizzard only recognized boo
extern "C" string1_API void foo(char* fpar1, double fpar2, double* farr3[5]);
extern "C" string1_API void boo(char* bopar1, double bopar2, double* boarr3[5]); //works
memcopy worked, before the array returned only the address in cell 1
also needed to do memcopy for the string, otherwise it was just the 1st letter
int stringlength = 128;
string errorMSG = "no error, nice.";
memcpy(rpar3, errorMSG.c_str(), stringlength);
rpar3[stringlength - 1] = '\0';
now its a happy roo. Beeing in 'straja and all that.
wow, it hooked up, showed this (because no inputs yet). But connected to dll, got error from there. Relief
12-17-2024 09:12 PM
I clean up the code and attach
12-17-2024 09:49 PM - edited 12-17-2024 10:30 PM
header for wrapper
#pragma once
using namespace std;
#define DllImport __declspec( dllimport )
#define DllExport __declspec( dllexport )
#ifndef LABVIEW_H_
#define LABVIEW_H_
extern "C" DllExport void TC_inputs(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 toLVarray[20], char* toLVerror_string);
#endif;
wrapper source code
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <iostream>
#include <array>
#include "labview.h"
#include "structHeader.h"
#include <string>
#include <vector>
using namespace std;
//--------- get all the classes here then they don't have to be in the main function
TurboCalcCompressor compressor;
TurboCalcParameters parameters;
TurboCalcUnitOverrides unitOverrides;
TurboCalcResults outputData;
TurboCalcError turboCalcError;
double *outsArray[20] = { 0, 0, 0, 0, 0 ,0, 0, 0, 0, 0 ,0, 0, 0, 0, 0 ,0, 0, 0, 0, 0 };
//----------calculation input values
void TC_inputs(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 toLVarray[20], char* toLVerror_string)
{
//compressor settings
compressor.typecode = fromLVcomp;
strcpy_s(compressor.modelVersion, SHORT_STRING_LENGTH, "0");
compressor.refrigerant = fromLVrefrigerant;
parameters.calculationType = fromLVcalcType;
strcpy_s(parameters.globalUnits, SHORT_STRING_LENGTH, "SI");
//these are real time form testrig
parameters.inputQuantity = fromLVkW;
parameters.saturatedDischargeTemperature = fromLV_SDT;
parameters.saturatedSuctionTemperature = fromLV_SST;
parameters.suctionSuperheat = fromLV_SSH;
parameters.subcoolingTemperature = fromLV_SC;
parameters.approachTemperature = fromLV_App;
parameters.useEcon = fromLVecon;
parameters.ambientTemperature = 25; //leave at 25 for testrig.
parameters.deratedFullLoadAmps = 0; //leave at 0.
//Pass in the created objects to Turbocalc getCompressorEfficiency to get the compressor's efficiency.
getCompressorEfficiency(compressor, parameters, unitOverrides, outputData, turboCalcError);
//put all turbocalc results into an array so Labview can pull it apart
double TC_array[20];
TC_array[0] = outputData.coefficientOfPerformance.min;
TC_array[1] = outputData.coefficientOfPerformance.optimal;
TC_array[2] = outputData.coefficientOfPerformance.max;
TC_array[3] = outputData.electricalInputPower.min;
TC_array[4] = outputData.electricalInputPower.optimal;
TC_array[5] = outputData.electricalInputPower.max;
TC_array[6] = outputData.capacity.min;
TC_array[7] = outputData.capacity.optimal;
TC_array[8] = outputData.capacity.max;
TC_array[9] = outputData.evaporatorMassFlow.min;
TC_array[10] = outputData.evaporatorMassFlow.optimal;
TC_array[11] = outputData.evaporatorMassFlow.max;
TC_array[12] = outputData.outletTemperature.min;
TC_array[13] = outputData.outletTemperature.optimal;
TC_array[14] = outputData.outletTemperature.max;
TC_array[15] = outputData.inletEntropy;
TC_array[16] = outputData.inletEnthalpy;
TC_array[17] = outputData.outletIsentropicEnthalpy;
TC_array[18] = outputData.econSaturatedTemp;
TC_array[19] = outputData.interstagePressure;
memcpy(toLVarray, TC_array, 20 * sizeof(double)); //do C command, memcopy array otherwise only memory address shows up
string errorMSG{};
errorMSG = turboCalcError.errorMessage;
int stringlength = 128;
memcpy(toLVerror_string, errorMSG.c_str(), stringlength); //need memcopy, otherwise only 1st letter appears
toLVerror_string[stringlength - 1] = '\0';
}
now its just this one left
hm, maybe because of this, 32 bit allocaded but string length defined as 256