LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

DLL call, once more: help wanted

Solved!
Go to solution

Hi! What I would like to recommend for you: take any suitable C-compiler (Microsoft Build tools sufficient, or NI CVI), then create your own DLL with exactly the same interface, then call it from LabVIEW and check how the parameters passed. Also helpful to create command line tool and execute under debugger, so you will see what happened under the hood.

You can add one more parameter to ensure that all parameters transferred correctly, something like that:

 

extern "C" __declspec(dllexport) int pn_epm_lookup(pn_dev_t dev, pn_addr_t addr, epm_lookup_t *buf, size_t *buflen, int *test)
{
test[0] = addr.a;
test[1] = addr.b;
test[2] = addr.c;
test[3] = addr.d;
test[4] = addr.deviceInstance;
test[5] = addr.deviceID;
test[6] = addr.vendorID;
test[7] = addr.port;
 
test[8] = buf->interfaceID;
test[9] = buf->versMajor;
test[10] = buf->versMinor;
test[11] = buf->deviceInstance;
test[12] = buf->deviceID;
test[13] = buf->vendorID;
test[14] = buf->port;
 
return 0;
}

 

Now, assumed that you're under 32 bit and using cdecl as calling convention. According to this, all parameters passed via stack. You have two structures (first and last parameters in your function are trivial). The first structure placed completely on the stack, the second one - is just address (on the stack as well). This is the reason why you can't use array for the first structure. Each parameter will be aligned to 4 bytes, this is the reason why you need to pack your chars and shorts into integers (and in proper byte order, of course) and pass 255 chars for the identifier as 64 32-bit values. For the second struct you can fill the array.

 

As result you have a call like this:

Screenshot 2023-08-17 16.15.22.png

Buf array called using data pointer:

Screenshot 2023-08-17 16.25.52.png

 

This is what Rolf mentioned above.

 

Or, if you prefer to have Cluster, you can do it as well:

Screenshot 2023-08-17 16.17.01.png

 

then adapt to type:

2023-08-17 16.27.38 - Call Library Function.jpg

 

but here will be a little be more more difficult to insert/extract the annotation[64] string information in them if needed.

 

Its will be more elegant to create a wrapper, which will get native LabVIEW types, then wrap to desired structures.

 

Source code of DLL:

 

Spoiler
#include <utility.h>
#include "cvidef.h"
 
typedef void *pn_dev_t;
 
typedef struct // Length by defintion: 267
{
unsigned char a; /**< IP address, first component */
unsigned char b; /**< IP address, second component */
unsigned char c; /**< IP address, third component */
unsigned char d; /**< IP address, forth component */
unsigned short deviceInstance; /**< InstanceID, NodeID or LocalIndex */
unsigned short deviceID; /**< Device ID */
unsigned short vendorID; /**< Vendor ID */
unsigned short port; /**< UDP Port of the PNIO instance */
char initiator[255]; /**< Initiator station name */
} pn_addr_t;
 
typedef struct // Length by definition: 84 bytes
{
/**
* The found interface ID.
* Valid values:
* - 1 - Device
* - 2 - Controller
* - 3 - Supervisor
* .
*/
int interfaceID;
/** The major interface version number. */
int versMajor;
/** The minor interface version number. */
int versMinor;
 
/**
* Device Instance Number.
* Part of the the PROFINET address information.
*/
unsigned short deviceInstance;
/**
* Device Identification.
* Defined by the vendor. Can be used to distinguish between several
* different device types, versions and so on.
* Part of the the PROFINET address information.
*/
unsigned short deviceID;
/**
* Vendor of the device.
* Uniquley identify the vendor of the device. Part
* of the PROFINET address information.
* \see http://www.profibus.org/ for more informations and a list
* of all defined vendors.
*/
unsigned short vendorID;
 
/** Device annotations. */
char annotation[64];
 
/**
* Port Number.
* UDP Port used by this PROFINET instance
*/
unsigned short port;
 
} epm_lookup_t;
 
 
extern "C" __declspec(dllexport) int pn_epm_lookup(pn_dev_t dev, pn_addr_t addr, epm_lookup_t *buf, size_t *buflen, int *test)
{
test[0] = addr.a;
test[1] = addr.b;
test[2] = addr.c;
test[3] = addr.d;
test[4] = addr.deviceInstance;
test[5] = addr.deviceID;
test[6] = addr.vendorID;
test[7] = addr.port;
 
test[8] = buf->interfaceID;
test[9] = buf->versMajor;
test[10] = buf->versMinor;
test[11] = buf->deviceInstance;
test[12] = buf->deviceID;
test[13] = buf->vendorID;
test[14] = buf->port;
 
return 0;
}
 
//==============================================================================
// DLL main entry-point functions
 
int __stdcall DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
if (InitCVIRTE (hinstDLL, 0, 0) == 0)
return 0;   /* out of memory */
break;
case DLL_PROCESS_DETACH:
CloseCVIRTE ();
break;
}
 
return 1;
}

 

Code (LabVIEW2018SP1/32-bit) is attached, hopefully will be helpful for you.

Message 11 of 26
(908 Views)

@MaSta wrote:

 


@rolfk wrote:

If you want to pass the second cluster as a byte array then for Gods sake don't configure it as byte array, passed as handle but instead pass it as C array pointer! 


 There is no such option.

MaSta_1-1692261138020.png


I'm not working with a German LabVIEW but the Array-Datenzeiger is the German translation for Array Data Pointer.

 

My frustration comes from the fact that I have answered quite a few of your posts about interfacing to DLLs (and more about serial communication) and somehow I get the feeling we are starting all over again every time.

Rolf Kalbermatter
My Blog
Message 12 of 26
(886 Views)

@rolfk

It looks like this, yes, but it's a new struggle. With the help of people like you, I had learned how to adapt DLL calls to structs back when I was interfacing a Profibus DLL to LabVIEW and there it worked out well. This one function refuses every type of attempt.

 

In your previous post you wrote "C array pointer", and because there is actually an option with that name, but not for arrays, I was confused. In the attached PN_EPM_Lookup.vi, the buf variable (here: Instance) was actually declared as "Array data pointer". That's why I wondered why you suggested me things I had already tried and which where in the VI, supposing you downloaded the code. The deactivation structure contains several attempts which all failed.

 

That's why I started to create the wrapper DLL. There it doesn't matter how I pass the data to the wrapper function, as long as pn_epm_lookup() is passed the data as expected. But guess what?`Visual Studio fails to create the wrapper DLL and I don't know why. It says it cannot find the pn_epm_lookup(). The exact same thing done (I made two test projects) in a console application builds just fine. I'm now finding out, if a Win32 C DLL from within a Win32 C++ DLL is a no-go, but that would make me doubt everything.

0 Kudos
Message 13 of 26
(870 Views)

I did download your code but could not look at it since I only have LabVIEW 2018 and with a bit of effort 2019 currently accessible.

Rolf Kalbermatter
My Blog
0 Kudos
Message 14 of 26
(862 Views)

@Andrey_Dmitriev

Wow! Many thanks. 

Array vs. struct: my attempt to feed a simple struct by its single members is similar to what you suggest, but still different. I always thought if I feed an array and in the declaration I set the "Minimum size" to what the array shall have, it would be the same as adding 64 inputs.

 

Endianess: it should also suffice to simply revert the 192 168 0 2 in the array to 2 0 168 192, but I actually tried that and it didn't do the trick.

 

"but here will be a little more more difficult to insert/extract the annotation[64] string information in them if needed." - Actually not, when using the MoveBlock function. At the moment, the goal is to run the function without error.

 

"Its will be more elegant to create a wrapper": I'm currently on that, trying first with this particular function, later perhaps adding everything else.

The advantage of the DLL is that I can debug. But as I already answered to Rolf, the linking fails and I can't figure out why. This is a LabVIEW forum, so asking here for VS stuff is not OK. Another forum, StackOverflow, rejected my problem because too basic.

0 Kudos
Message 15 of 26
(847 Views)

@rolfk wrote:

I did download your code but could not look at it since I only have LabVIEW 2018 and with a bit of effort 2019 currently accessible.


Oh,OK, sorry. I made a 2018 version.

0 Kudos
Message 16 of 26
(844 Views)
Solution
Accepted by topic author MaSta

This would be a quick and dirty stab at what I would do for this function:

rolfk_0-1692378774856.png

If this crashes you should also check what WINDLL_PROFINET means. It might be defined to be a specific calling convention such as __stdcall, but that definition is not in the profinet.h file (most likely it is in profinet_config.h) Then the calling convention in the Call Library Node might need to be adjusted.

Rolf Kalbermatter
My Blog
0 Kudos
Message 17 of 26
(831 Views)

#define WINDLL_PROFINET __declspec(dllexport) -> nothing LabVIEW related

 

@rolfk

Thank you very much. I tried and it works without error when calling the original DLL (Andreys DLL didn't call the original DLL so I couldn't use it). Yay! I still don't fully understand why, so I got questions for my understanding and learning, not because I doubt anything:

 

a) why would we/you create the initiator array as I32 and not U8? Wouldn't the function receive way to many bytes? Ok, it's a pointer

b) why not pass the simple struct "addr" as single inputs, as NI suggests? Do you see problems here, from your own experiences?

c) why pass NrInst as USZ when NI says, that DLL calls do automatic referencing and dereferencing?

d) the FOR loop is used to bring member "Annotation" into the correct length, but what is the search VI for? I changed it to length 64.

 

Now having a solution for LabVIEW, I'm wondering if I should still make a wrapper DLL, though I still couldn't figure out why it would compile. That DLL would make passing data much easier. The effort for interfacing this one function was huge.

 

P. S. There is one weird thing left. In the data coming from the EPM lookup, there is the VersionMinor. It comes out differently from LabVIEW and VS. And this isn't because of endianess.

MaSta_0-1692606421061.png   

MaSta_1-1692606446960.png

 

 

0 Kudos
Message 18 of 26
(806 Views)

a) to pass the 255 bytes over the stack by value it needs to be passed as 64 32-bit values! If you pass 255 u8 Parameters instead you mess up the stack! Rather than turning the string into a byte array to then clumsily combine 64 times 4 bytes into an int32 to pass as individual stack variable, I directly convert it into an array of 32-bit values. Most likely that array will not contain 64 integers, as LabVIEW will only generate an array that is long enough to contain the incoming string, most likely 0 elements since the string is empty, but the Index Array afterwards simply returns a 0 value for non existing array elements.

 

b) if you pass the cluster as Adapt to type, LabVIEW will pass the equivalent of "pn_addr_t *addr" on the stack, aka a pointer to the struct, but we want "pn_addr_t addr" meaning the struct flattened onto the stack (at least in 32-bit Windows, 64-bit Windows and Linux GCC are different). This has to do with the platform ABI (Application Binary Interface specification that is dictated by the platform and sometimes also the used compiler).

 

c) USZ has nothing to do if the parameter is passed by reference or value, that would be the Pass by Value or Pass as Pointer configuration. Instead it means that this is a pointer sized unsigned integer, which corresponds to the the size_t in the prototype.

 

d) The Search Array function looks for the first NULL byte and terminates the array on that index. A NULL byte is the C indication for the string end. If you leave it 64 bytes it will usually contain lots of NULL bytes at the end that are meaningless.

Rolf Kalbermatter
My Blog
0 Kudos
Message 19 of 26
(791 Views)

Thanks again, for your patience and time.

 

There are two functions left I need to integrate, pn_rec_read() and pn_rec_write(). These do the actual DP-V1 communication. When using the DLL import wizard on the functions in the Profinet stack DLL, LabVIEW used some hidden VIs to create and destroy pointers. I found them in another caller VI. The caller VI for pn_rec_read() works without error (I see the Wireshark traffic), the requested data comes out of the call node, but the dereferencing of pointer Data doesn't work. This is LabVIEW's attempt:

MaSta_0-1692616487644.png

Seems logical, actually does the same as MoveBlock: 

MaSta_1-1692616676765.png

whereas LabVIEW crashes upon calling MoveBlock this time. I used MoveBlock successfully in another VI with a function call where the function itself returns a pointer. The pointer created by the DSNewPtr.vi comes out on Data. Trying a different data type for the dereferencing, like an I32 array, also didn't work. I now suppose that the pointer is bad.

 

 

0 Kudos
Message 20 of 26
(783 Views)