03-03-2023 11:21 AM
LV 2022 x64
I'm needing to call a function that has a struct of 2x U32s as members and is passed by-value and not as a pointer. However if I try sending 2x U32s LabVIEW seems to be doing some sort of packing accounting which it shouldn't be doing for separate arguments, and the Network member of the struct vanishes and other arguments are offset oddly. I can get the call to work correctly if instead of sending a cluster of 2 U32s, I send the 2 U32s packed into a single U64. I purport that there's a bug in how the CLFN is managing the data sizes, NI told me I was using it incorrectly even though I sent them a method that does work despite not being the expected method and their response doesn't work correctly.
I'm hoping the hive mind may be able to confirm a bug or tell me what I'm misunderstanding about the first method I tried using for the call.
The struct definition is:
typedef struct {
uint32_t NetworkMask;
uint32_t Network;
} AddressTranslation_t;
And the function declaration is:
LV_EXPORT
extern uint32_t TestCall(
uint32_t TimeoutMs,
const struct sockaddr* BindAddress,
const AddressTranslation_t RequestTranslation,
Config_t** Configurations,
uint32_t* ConfigurationLength,
char** ErrorDescription
);
With the definition:
{
try {
sockaddr_in* sock = (sockaddr_in*)BindAddress;
*ConfigurationLength = sock->sin_addr.S_un.S_addr;
CreateErrorDesc("Greetings from DLL", ErrorDescription);
}
catch (std::exception e) {
CreateErrorDesc(e.what(), ErrorDescription);
return false;
}
return (RequestTranslation.Network >> 1) + (RequestTranslation.NetworkMask);
}
The function basically uses the pointers that are passed in to verify some of the other values make it through as expected and creates a return value that ensures the struct members make it intact.
Here is how I thought it would work but doesn't:
Here is how I'm able to get it to work:
And here is what NI sent me back:
The entire thing is attached. I'll post a screenshot of the call stack once my visual studio subscription is straightened out if that'd be helpful to show what LabVIEW is doing wrong about the first method and hopefully the function prototype of the third method alone is enough to recognize why that won't work, which is apparently what the shared library import wizard generates that I wasn't able to run on the full library and hadn't bothered trying on this smaller reproduction.
Solved! Go to Solution.
03-03-2023 01:03 PM
You are likely using 64-bit LabVIEW (and DLL) and there a parameter is of course always a 64-bit entity, so your 2 * 32-bit elements need to be packed in a 64-bit integer.
03-03-2023 01:41 PM
Really? LabVIEW is incapable of putting U32s on the stack? Or any other size? How is it supposed to call a function with a byte or I16 or U32 as a function argument?
03-03-2023 01:41 PM
I'll also point out that the timeout first argument is a U32 and doesn't cause oddities so I'm going to have to call bull on that statement 🙂
03-03-2023 02:14 PM - edited 03-03-2023 02:41 PM
Read about ABI logic. The Windows 64-bit ABI has a mixed register/stack parameter passing called fastcall. First four parameters are passed in 4 64-bit registers, with 4 64-bit shadow locations on the stack for them and the rest is aligned on 64-bit boundaries on the stack. Parameters not fitting into 64-bit are passed as reference (64-bit pointer) If a 8-bit char parameter has to be passed it is put into a 64-bit register or stack location, possibly sign extended for signed integers. Your struct with 2 32-bit values fits into a single 64-bit value, is a single parameter and is therefore passed as a single 64-bit value in a register or on the stack.
For 32-bit LabVIEW it would be distributed over two 32-bit parameters. Passing structs by value is a very strange beast indeed and causes this kind of differences.
03-03-2023 04:26 PM
Thanks for that topic name Rolf, that's the kind of thing I was hoping for. And thanks for the edits and more clarification.
After looking at the stacks again I'm pretty sure I see where my confusion came from. At first glance it looked like things weren't aligned on 8 byte boundaries:
But now I'm thinking that first 0x1DB sequence is just stale memory left in place from a previous operation.
Taking a look again at the working method there's an obvious 8 byte alignment happening which I think I missed the extra "padding" on the timeout value the first time around:
03-04-2023 12:41 AM - edited 03-04-2023 12:47 AM
Yes, the padding is not required to be initialized for prototyped parameters. The assumption is that since there is a prototype, both the caller and callee agree on the type and don’t care about the contents of the padding.
Also I’m not entirely sure the caller is required to write the first 4 parameters on the stack as they are passed in the registers. But I suppose it’s safer to do it anyways just to be sure.
03-04-2023 04:46 PM
Yeah that's what lead to the "bullshit" remark. They're in memory on the stack so it didn't add up with some of what was being said. The built DLL I'm debugging is using the values from the stack. (I found the memory location with the expression "&TimeoutMs" in the address field)
Looking at MSDN __fastcall uses registers from the caller but the callee can still use the stack which makes the register-sized-alignment make sense. I'm not finding where it says things default to fastcall though and I assumed stdcall... which this obvious isn't since stack items aren't reversed. And I just noticed where it says x64 ignores fastcall so I guess I'm back to not understanding the "why" but empirically see what's happening now, so *shrug*.
This wizard has so much more to learn 😆
03-05-2023 04:08 AM
Something you read is off. Fastcall is the default for Win64, unless it is an object method which still uses thiscall I believe.
https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170
Stdcall is not a thing in Win64 anymore and is the thing that should be ignored by a compiler and at least MSVC does so by default. Same about cdecl.
https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170
03-05-2023 01:18 PM
@rolfk wrote:
Parameters not fitting into 64-bit are passed as reference (64-bit pointer)
So that means on 64 bit Windows a struct passed by value in the function declaration is actually passed by reference if its size exceeds 8 bytes? I wish I'd known that before..