LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Passing cluster input as argument to dll

Solved!
Go to solution

Yes your observation about Python is principally right, but look at the header that LabVIEW generates when you generate a DLL. If you specified to pass native LabVIEW array datatypes instead of C pointers, it also adds according Alloc.., Resize... and DeAllocate.. function exports for every different variable sized LabVIEW datatype that you use. When you import them too in your Python interface script you can properly handle those values in a way that the DLL is happy. And since the DLL itself exports these functions it is also guaranteed that they use the same memory manager runtime that the DLL itself uses in its VI function(s).

 

Definitely use these functions if you need to provide LabVIEW compatible arrays. Trying to use DSNewHandle and friends directly is a real pitta. First you would need to find out which LabVIEW runtime the DLL is interfacing with. The first issue is to find out the LabVIEW version, and if the DLL is using the exact version or possibly a newer one than the LabVIEW version it was compiled with. Usually this would be lvrt.dll and you might be successful to do a GetModuleHandler("LVRT.DLL") AFTER you made sure you loaded the LabVIEW DLL. But, things get very very nasty, if you ever intend to use two different LabVIEW DLLs that might have been compiled in different LabVIEW versions. In that case each DLL may use its own specific Runtime and you have more than one LVRT.DLL loaded into your process. Good luck to find out which one is the right one, and no, using the wrong one will NOT be harmless!!

Add to that that there are actually other options that a DLL could load as LabVIEW runtime, including being invoked in the LabVIEW IDE itself or a little known lvrtff.dll variant which is basically a LabVIEW runtime engine with many edit time features still included. Unlikely to happen when trying to use LabVIEW DLLs, but not impossible.

Rolf Kalbermatter
My Blog
0 Kudos
Message 11 of 24
(282 Views)

Got little bit of interesting result from following your python code. My outputArray was still full of zeros. I am using Labview 2019 x32 and python 3.11.5 x32 also, but my machine is 64 bit. I would not be really surprised if this mismatch leads to some unexpected behaviors. Can you share the settings you have for the build specification, just to make sure I am not doing anything obviously wrong?

0 Kudos
Message 12 of 24
(260 Views)

@Abhi_kool wrote:

Got little bit of interesting result from following your python code. My outputArray was still full of zeros. I am using Labview 2019 x32 and python 3.11.5 x32 also, but my machine is 64 bit. I would not be really surprised if this mismatch leads to some unexpected behaviors. Can you share the settings you have for the build specification, just to make sure I am not doing anything obviously wrong?


When you working with 32-bit environment, you should keep in mind two very important things: memory alignment and calling conventions.

 

The memory layout of a cluster depends on the platform you are running. LabVIEW may add padding between cluster elements so that those elements are aligned to certain addr....

 

Usually from C code point of view it looks like that:

#ifndef WIN64
#pragma pack(push,1)
#endif

// Your structs definitions here

#ifndef WIN64
#pragma pack(pop)
#endif

And cdecl/stdcall are matter, they are different on 32-bit. Refer to Calling Conventions

Message 13 of 24
(256 Views)
Solution
Accepted by Abhi_kool

Yes, just checked in 32-bit LabVIEW 2023 and Python 3.12.2, both 32-bit, this is how it works:

 

DLL Code:

#ifndef WIN64
#pragma pack(push,1)
#endif

typedef struct {
	int32_t dimSize;
	double elt[1];
} DoubleArrayBase;

typedef DoubleArrayBase **DoubleArray;

typedef struct {
	DoubleArray input_array;
	double Numeric;
} Cluster;

#ifndef WIN64
#pragma pack(pop)
#endif

void __cdecl __declspec(dllexport) func(Cluster *input_cluster, double output_array[], int32_t len)
{
	for(int i = 0; i < len; i++){
		if (!input_cluster) break;
		if (!input_cluster->input_array) break;
		output_array[i] = (*(input_cluster->input_array))->elt[i] * input_cluster->Numeric;
	}
}

 

Python code:

 

import ctypes as ct

# Load the DLL
mydll = ct.CDLL(r"PATH_TO_YOUR_32_BIT_DLL.dll")

# size of the input Array
dimSize = 3

# Define the structures in Python
class DoubleArrayBase(ct.Structure):
    _pack_ = 1
    _fields_ = [
        ('dimSize', ct.c_int32),
        ('Numeric', ct.c_double * dimSize)
    ]

DoubleArray = ct.POINTER(ct.POINTER(DoubleArrayBase))

class Cluster(ct.Structure):
    _pack_ = 1
    _fields_ = [
        ('input_array', DoubleArray),
        ('Numeric', ct.c_double)
    ]

# Define the function signature
func = mydll.func
func.argtypes = [ct.POINTER(Cluster), ct.POINTER(ct.c_double), ct.c_int32]
func.restype = None

# Create input data
input_cluster = Cluster()
input_cluster.input_array = DoubleArray()

# Prepare output array
output_array = (ct.c_double * dimSize)()

DoubleArr = ct.c_double * dimSize

# Test Data
input_cluster.Numeric = 40.0
vec = DoubleArr(10.0, 20.0, 30.0)

input_cluster.input_array = ct.pointer(ct.pointer(DoubleArrayBase(dimSize=dimSize, Numeric=vec)))

# Call the function
func(ct.byref(input_cluster), output_array, dimSize)

# Print the result
print(list(output_array))

 

Don't forget _pack_ = 1

 

And result:

 

>python dummyPyDemo.py
[400.0, 800.0, 1200.0]
0 Kudos
Message 14 of 24
(245 Views)

It would be helpful if there is a conditional around the pack line

 

import sys

is_64bits = sys.maxsize > 2**32

......

# Define the structures in Python
class DoubleArrayBase(ct.Structure):
    if not is_64bits:
        _pack_ = 1
    _fields_ = [
        ('dimSize', ct.c_int32),
        ('Numeric', ct.c_double * dimSize)
    ]

 

Otherwise when running in 64-bit Python with 64-bit DLL, this pack directive will be wrong since LabVIEW uses default alignment on non Windows 32-bit platforms (disregarding old platforms that aren't supported since many moons). It should actually be even more extended to check for Windows AND NOT 64-bit!

 

Most elegant would be to define first a class LVStructure that derives from ctypes.Structure and simply adds this conditional pack field, then derive all structures that are passed to LabVIEW DLLs from this new class.

 

Also the calling convention should probably be conditional too. LabVIEW really knows 3 different calling conventions:

 

On all 32-bit platforms __cdecl

On Windows 32-bit also __stdcall

On all 64-bit platforms only native, which is __fastcall on Windows and System V on Linux and Mac OS, which is similar to Microsoft's __fastcall except that it passes the first 6 parameters in registers in comparison to the first 4 for the Microsoft 64-bit calling convention.

 

So basically there is only one possible calling convention on all platforms except Windows 32-bit.

Rolf Kalbermatter
My Blog
Message 15 of 24
(234 Views)

Just one more thing — if you prefer to have stdcall instead, and your function declared as

 

void __stdcall __declspec(dllexport) func(Cluster *input_cluster, double output_array[], int32_t len)

 

Then you should call it as

 

# Load the DLL
mydll = ct.WinDLL(r"YOUR_32-BIT_STDCALL_DLL.dll")

 

And obviously in LabVIEW

 

DLL.png

 

Now its complete more or less.

 

And one more hint if you working in "mixed" environment:

 

"There are a few special wildcards that you can also use in the DLL name which LabVIEW will first substitute before passing the path or name to Windows.

 

name.*        LabVIEW will replace the wildcard with the file ending for the current platform, dll on Windows.

name*.dll    LabVIEW will replace the wildcard with 64 when running in 64-bit and 32 otherwise.

name**.dll   LabVIEW will replace the double wildcard with _64 when running in 64-bit and nothing otherwise.

 

The wildcard for the latter two cases can be placed anywhere in the name before the dot to indicate the name ending. So "name*shared.dll" will result in "name32shared.dll" in 32-bit LabVIEW. This can be handy if you do not want to place a DLL into the system directory but rather want to keep it in the VI library directory. This way you can place both the 32-bit and 64-bit in the same directory and LabVIEW will simply use the one that is appropriate."

 

Refer to Re: Working with 32bit and 64 bit in the same project

Message 16 of 24
(222 Views)

Wow guys. Thank you for your overwhelming support. I will definitely check out the new suggestion. But a question where did you learn all this stuff? Its not very direct nor apparent from the documentation at all. But please drop some links to the resources that you think would benefit me from learning!

0 Kudos
Message 17 of 24
(215 Views)

@Abhi_kool wrote:

But a question where did you learn all this stuff? Its not very direct nor apparent from the documentation at all. But please drop some links to the resources that you think would benefit me from learning!


Lot's of Google foo and by now 30 years of digging into how to interface to external code in LabVIEW. 😀

I actually bought back in those days a Watcom C license, to be able to create LabVIEW CINs, which was the LabVIEW proprietary predecessor to DLLs/shared libraries.

Rolf Kalbermatter
My Blog
0 Kudos
Message 18 of 24
(207 Views)

@Abhi_kool wrote:

Wow guys. Thank you for your overwhelming support. I will definitely check out the new suggestion. But a question where did you learn all this stuff? Its not very direct nor apparent from the documentation at all. But please drop some links to the resources that you think would benefit me from learning!


Glad to see it was helpful for you (I've learned also a little bit as well).

 

Some links was already provided:

 

How LabVIEW Stores Data in Memory

 

Call Library Function Node (+ all nested links)

 

Configuring the Call Library Function Node

 

x64 calling convention

 

x86 calling conventions

 

Google & Stack Overflow + NI Forum are also your friends. Attached pretty old Doc from NI (I haven't any newer), mostly valid for modern LabVIEW versions, but you can skip Chapters 3-5, because related to CINs, but they got dropped as mentioned by Rolf above.

+ many years experience, lot of experiments and crashes and so on.

 

A very good exercise, which I would to recommend is to disassemble your own DLL and check what happened inside under the hood (you can use any free available disassembler like IDA Free or Ghidra). Also run it under debugger like WinDbg or x64dbg. This kind of reverse engineering is very helpful to understand how the data passed to DLL and then accessed, what happened with stack, etc.

Message 19 of 24
(204 Views)

FYI, the pack thing did the trick! Also something to keep in mind is that the output should be "Array data pointer", if I give it as "handle pointer" then the value is not put to output_array. I have no idea why this is so, but I assume it has something to do with memory management once again. But really thank you very much you two. I will start to look into the resources and links you have shared! 

0 Kudos
Message 20 of 24
(181 Views)