LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

DLL call, once more: help wanted

Solved!
Go to solution

Hello everyone!

I once more came across a DLL call I tried and tried to bring into LabVIEW, but didn't succeed. The function is defined as:

WINDLL_PROFINET int pn_epm_lookup(pn_dev_t dev, pn_addr_t addr, epm_lookup_t *buf, size_t *buflen);

whereas

  • pn_dev_t is a handle (U32 or I32 value)
  • pn_addr_t is a struct (cluster, I use it as constant, because it is one, but setting it to non-constant doesn't change anything)
  • epm_lookup_t is a struct (cluster)
  • size_t is an I32

The problem are supposed to be the structs or at least the second one. Passing them I could do several things:

  1. Create the struct according to the header file, including pointer alignment to make the byte size equal to what C code would generate in memory
  2. Create a byte array of the struct's size and pass it -> the DLL function wouldn't notice
  3. For the 2nd struct I could pass a single struct or array of structs or even a U8 array

As for the settings of the parameters, a direct struct or struct array passing would be "Adapt to type" and "Handles by value", though struct array passing can be complicated, and for a U8 array it would be "Array", "U8" and "Array pointer".

Both don't work. I tried everything I learned from https://forums.ni.com/t5/Developer-Center-Resources/Passing-and-Receiving-Pointers-with-C-C-DLLs-fro...

The C code is simple:

 

		pn_addr_t addr;
		addr.a = 10;
		addr.b = 0;
		addr.c = 0;
		addr.d = 2;
		// lookup pnio instances
		epm_lookup_t instances[20];
		size_t len = 20;
		/* perform EPM lookup */
		handle_pn_error(pn_epm_lookup(device, addr, instances, &len));

 

whereas pn_addr_t is 267 bytes in total (aligned to 268) and epm_lookup_t is 84 bytes, passed as an array of 1680 bytes. The C code would return some data in "instances" and error code 0.

In LabVIEW, the call returns an error and no data.

 

Note: you can open and run the VI, but it will return error 6 because of a missing licence dongle. I just added the files to have code to look at.

 

TIA for any help.  

0 Kudos
Message 1 of 26
(1,259 Views)

And what are we supposed to look at? No VI at all. Also it would be useful to have the headers to look at for a few checks. Looking for that API in Google returns exactly one hit, to this post!

For VIs you post Save for Previous is your (and our) friend if you use LabVIEW 2020 or newer.

Rolf Kalbermatter
My Blog
0 Kudos
Message 2 of 26
(1,221 Views)

Oh, this was the "session expired" problem, as it seems. I had added the code and left the "Create new topic" page open for some hours, then LV reported this timeout, I updated the page and didn't notice the attachments were gone. Even though it saves the draft...

Attached now.

 

By the way, while waiting for an answer I started to create a wrapper DLL, but didn't succeed so far. Even VS has simple problems.

Download All
0 Kudos
Message 3 of 26
(1,183 Views)

pn_dev_t and pn_slave_t is a pointer, so defining it as uInt32 is only correct for 32-bit LabVIEW.

 

pn_addr_t addr is passed by value!!!! That means you need to pass the various elements in that structure piece by piece as individual parameter but the individual parameters are 32-bit aligned on 32-bit LabVIEW and 64-bit aligned by 64-bit LabVIEW. So as an example you would have to combine the a, b, c, d into a 32-bit integer and pass that as one parameter, then deviceInstance and deviceID as another 32-bit parameter. and finally vendorID and port into a third 32-bit parameter. and now for the fun part, you need to add 64 more 32-bit parameters to reserve stack space for the initiator string. After these 67 plus the initial device parameters, there finally follows the pointer for the epm_lookup_t *buf parameter. Very much fun!

 

For 64-bit LabVIEW (and DLL) things would again look considerably different. The Windows 64-bit ABI specifies that structure parameters passed by value that do not fit into a 64-bit value, are passed by reference anyways.

Rolf Kalbermatter
My Blog
0 Kudos
Message 4 of 26
(1,162 Views)

Thanks, but I'm not sure if you refer to 

WINDLL_PROFINET int pn_epm_lookup(pn_dev_t dev, pn_addr_t addr, epm_lookup_t *buf, size_t *buflen);

 

About the alignment: as far as I learned from having such issues earlier with another DLL for Profibus, the alignment is only important within a structure passed to the function, not between the parameters. However, it's worth a try to also align the parameters, but with 268 bytes first and then 84 bytes, both are actually fine. 

 

64 bit is no topic. Who need's that anyway?

0 Kudos
Message 5 of 26
(1,145 Views)

Well addr is passed by value, buf is passed by reference (pointer). And that's definitely very important (and makes in 32-bit a huge difference). Passing such monster structures by value is however definitely a developer mistake. But done is done, they won't change that now!

 

As to who needs 64-bit: Rather sooner than later this will be the only version that will still be supported in new software.

Rolf Kalbermatter
My Blog
0 Kudos
Message 6 of 26
(1,130 Views)

addr: I tried to pass by value, like the KB article suggested for simple structs, and also as array by pointer. The result is the same, only the execution time of the call varies. From my understanding, I only need to find a way for LabVIEW to pass the correct data to the function, which is in the memory it accesses. LabVIEW can do that, but since there are so many options it's hard to find the correct one.  

 

My best guess is that the called function can somehow not access the IP in addr and runs into a timeout. Unfortunately, the error 65 I get isn't documented by the maker of the DLL. Asking them is cumbersome as they don't care about LabVIEW use. The DLL was made for C applications.

 

So far, I have no solution. Will try the wrapper DLL.

0 Kudos
Message 7 of 26
(1,100 Views)

Wrapper DLL: 

for the DLL call in PN EPM Lookup.vi where epm_lookup_t is passed as struct array, I can create a C file. LabVIEW would turn this struct passing into a handle defined as

typedef struct {
	int32_t dimSize;
	TD2 elt[1];
	} TD1;
typedef TD1 **TD1Hdl;

and builds the function call as

int32_t pn_epm_lookup(uint32_t DeviceHandle, uint8_t IPOctet1, 	uint8_t IPOctet2, uint8_t IPOctet3, uint8_t IPOctet4, uint16_t InstIn, 	uint16_t DeviceID, uint16_t VendorID, uint16_t Port, uint8_t Initiator[], TD1Hdl InstOut, uint32_t *NrInst)

In that function I want to include the C code from the original C file that calls pn_epm_lookup(), but have trouble to assign the members of InstOut. I'm not as much into C and Visual Studio to know all that and VS tells me errors.

 

At first I have to hand the data pointed to by "Instout.elt[1]" to the function, which doesn't work that way. Any help?

I don't know to access the members of **TD1Hdl.

 

 

0 Kudos
Message 8 of 26
(1,069 Views)

You really should learn a lot more about C before trying to do these things. What you are trying to do currently is similar to shooting with a blindfold into the woods, hoping you catch some game. Pretty useless!

 

First, each parameter is 32-bit. If your structure has smaller elements than that, it needs to pack the values into a single 32-bit value and pass that to the parameter as 32-bit value. So your 4 single bytes values making up the IP address are a single 32-bit parameter. What the actual order is is of course a bit debatable, but since we are on a little endian CPU, the a value is the lowest significant byte of the uint32 and the d value would be the highest significant byte.

Same for the following two 16-bit values that need to be packed into a single 32-bit value in a similar way. And so on. Then you have the 255 chars for the identifier which needs to be passed as 64 32-bit values.

 

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! But you could also pass it as cluster when using the Adapt To Type type since it needs to be passed by reference. The annotation member would then be either an embedded cluster of 64 uInt8 values, or alternative 16 uint32 values but then it is more difficult to insert/extract the string information in them if needed.

 

Rolf Kalbermatter
My Blog
0 Kudos
Message 9 of 26
(1,057 Views)

Rolf, your comments are very helpful, but please be nice! Not everyone has the same level and learning is what I actually do here, also by trying. My C code skills come some years of writing code for 8 bit embedded controllers, but the Windows 32/64 bit world is different.

 

I might be wrong, but the trick of setting up DLL calls correctly in LabVIEW is to make LabVIEW transfer the data contained in any kind of datatype in the expected form to the DLL. I cannot know what LabVIEW actually makes internally out of a struct passed directly, a struct passed by handle or as U8 array. The export of the DLL call node as C code shows me it creates a struct with double pointer, embedding the original struct. In VS, I cannot dereference the handle.

 

For the actual DLL, which expects a certain order of bytes, it's logical to me, that a U8 array comes the closest, because it doesn't align members or changes byte orders.

 


@rolfk wrote:

First, each parameter is 32-bit. If your structure has smaller elements than that, it needs to pack the values into a single 32-bit value and pass that to the parameter as 32-bit value. So your 4 single bytes values making up the IP address are a single 32-bit parameter. What the actual order is is of course a bit debatable, but since we are on a little endian CPU, the a value is the lowest significant byte of the uint32 and the d value would be the highest significant byte.


I don't understand. Most parameters are not 32 bit. Passing the struct pn_addr_t to the function in the DLL works in a C environment with the IP being in the first 4 members. If I plot the memory content of "addr" byte by byte in the console, the IP octets are in correct order. 

Struct "buf" (pn_epm_lookup_t) is only filled by the DLL function, that's why a pointer, so its content doesn't matter when passing them. Supposedly, this should do just fine:

MaSta_0-1692260556001.png

 

Passing the complex struct as single struct or array, which it actually should be, didn't work. The replacement array shall not be passed as pointer and cannot be passed as value, which would actually be the right way. 

 


@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

 

0 Kudos
Message 10 of 26
(1,021 Views)