12-08-2021 03:37 PM
Hello NI,
I am having trouble with importing a DLL file to LabVIEW. I've gotten pretty far, but a few functions it couldn't make VIs for, and the
ones it could, they aren't executable because of the complex data structures used. I could really use an expert to help me walk through this process, but also confirm what I need in my cluster to use as a handle when using the call library node function.
The computer I am running this on is from a company called RTD Embedded Technologies, Inc. This computer is connected to and controls a FPGA board called the DM35424HR. This DM35424 board is what does all my ADC and DAC, and so on.
The company offers a zip file with example programs to be ran in 32 or 64-bit operating systems. Since the computer is 64-bit, I use the x64 examples and use 64-bit version of LabVIEW.
I have attached the DM35424Lib_x64.dll file.
To get the examples and drivers and all the libraries and headers and example programs (which are in C, C++)
you will need to download them by following the link here: https://www.rtd.com/software_drivers.htm
Scroll down until you see the DM35424 and then click the Windows Driver and Examples v01.00.00.
I can get 128 of the 136 functions by linking the right header files, and preprocessing a few data types but there are still some important ones that are missing. After that however, I get a report that shows none of the VIs are executable because of what I assume are the complex data structures, which I think are the C structs, and use pointers and handles and arrays as inputs. I am assuming I will need to build extremely complex clusters with the correct parameters to get the board functions to work using the call library node, or to fundamentally change the header files.
I tried using a uint64 numeric as the handle and passed as a pointer and I was able to connect three VIs without crashing, but then I got a 1097 error with the board close VI. I believe it is because of memory allocation and need to use the CleanUp() function which deallocates the memory used. I get this from the Adc.cpp and Dac.cpp examples. This CleanUp() function is one of the last 8 functions the wizard could not make a VI for.
For example: the function to open the board is called DM35424_Board_Open(board_num, &board);
board_num is an int and is always 0. &board I believe is an address to *board which is a pointer. It is a struct that was coded like this.
/**
* Pointer to the board descriptor used for this example
*/
struct DM35424_Board_Descriptor *board = NULL;
The board descriptor is a struct and was coded like this in the DM35424_board_access.h and the DM35424_os.h file
/**
@brief
DM35424 board descriptor. This structure holds information about
the board as a whole. It holds the file descriptor and ISR callback
function, if applicable.
*/
struct DM35424_Board_Descriptor
{
/**
A device-file handle used to communicate with the device via IOCTLs.
*/
HANDLE file_handle;
/**
Holds information about the board
*/
DM35424_Intrfc_Board_Info board_info;
/**
Holds handles for all the DMA callback threads.
*/
HANDLE dma_thread_handles[DM35424_MAX_FB][MAX_DMA_CHANNELS][MAX_DMA_BUFFERS];
/**
The termination events for the DMA callback threads.
*/
HANDLE dma_terminate_events[DM35424_MAX_FB][MAX_DMA_CHANNELS][MAX_DMA_BUFFERS];
/**
Holds handles for all the A/D interrupt callback threads.
*/
HANDLE int_thread_handles[DM35424_MAX_FB];
/**
The termination events for the A/D interrupt callback threads.
*/
HANDLE int_terminate_events[DM35424_MAX_FB];
} ;
The "DM35424_Intrfc_Board_Info board_info; " is another struct and located in the DM35424_interface.h file and is defined as
/**
A structure used in the IOCTL_DM35424_QUERY_BOARD IOCTL.
*/
typedef struct
{
/**
This index is a value unique to this board, out of all boards
controlled by this driver.
*/
uint32 index;
/**
This value is the total number of boards being controlled by this
driver.
*/
uint32 board_total;
/**
This value is the PCI vendor id of this board (from PCI configuration
space).
*/
uint16 pci_vendor_id;
/**
This value is the PCI device id of this board (from PCI configuration
space).
*/
uint16 pci_device_id;
/**
This is an array describing the register blocks being used by this
board.
*/
DM35424_Intrfc_Reg_Block_Info reg_blocks[DM35424_INTRFC_MAX_REG_BLK_COUNT];
/**
This value is the number of the interrupt that the board is using.
*/
uint32 irq_number;
/**
If this value is TRUE, the board being described is a bus master and is
capable of DMA. If this value is FALSE, the board being described cannot
perform DMAs.
*/
BOOLEAN is_bus_master;
/**
This is the size of the Common Buffer objects being used by the driver to
provide DMA access to the board. This value is the same across all hardware
DMA buffers for the board, but can vary from board to board. This value
also determines the maximum size of any individual DMA transfer.
This value is set when the driver is started for each board.
*/
uint32 common_buffer_size;
} DM35424_Intrfc_Board_Info;
My best guess is that I have to correctly make a cluster, that contains the ints, bools, arrays, and even a cluster of ints, arrays, bool, in the correct order to get this all to work and then pass it as "handle" and pass through values as pointers.
My apologies for the information dump. I really need help with getting this to work.
Best wishes,
James
12-08-2021
04:01 PM
- last edited on
05-20-2025
12:25 PM
by
Content Cleaner
Have you read:
https://www.ni.com/docs/en-US/bundle/labview/page/calling-shared-libraries.html
https://www.ni.com/docs/en-US/bundle/labview/page/configuring-the-call-library-function-node.html
They have info on clusters going into CLFNs.
12-08-2021 06:09 PM - edited 12-08-2021 06:26 PM
I can be fairly short about this:
Could you write a C program to access this API? If the answer is no, trying to interface the DLL from LabVIEW is basically doomed.
Using LabVIEW to access a DLL if you have to create the DLL binding, basically means that you need to know how to use that DLL in C and after that also a bit more about what a C compiler actually does in memory when compiling your code to call the DLL functions. So in a way it is even more knowledge you need to have than when you would call this DLL in C.
All the arrays in those structs are fixed size. This means they are inlined and not really array pointers. This is already the first mistake most people do when trying to access DLLs from LabVIEW. The next is to use LabVIEW arrays when there are C arrays. They are not the same. Then there is the maybe most popular one: In LabVIEW you do not worry about how big an array must be to pass to a function that creates data to write into it. The LabVIEW function simply resizes the array to whatever size it needs. This is in C not possible (without a very detailed and non standard memory management contract between the library and callee). Here the standard way is that the caller needs to know beforehand what array size will be needed, then allocates that array buffer, passes it to the function and after the function returns the data is maybe in the buffer.
If you do not know about all these things and a few dozen more like that, you create at best a library that will continuously crash, but possibly something that will not crash but still corrupt your process memory and sooner or later crash in a totally unrelated location anyways.
There are also several things that you can't really implement in LabVIEW itself. This API uses callbacks. That are functions pointers. There is no way to create function pointers in LabVIEW from LabVIEW code, so you can not assign LabVIEW VIs to handle those callbacks. To add insult to injury those callbacks receive some data structures but those data structures do not contain a reference to the board handle for which they apply. Instead you need to store the board handle in a global to access it from inside this callback!!!!! This is BAAAAAAAAD!
The FPGA ADC hardware may or may not be great. The API to access it for sure isn't!
12-09-2021 04:01 AM
As for a solution, 0-all of the problems could (potentially, maybe) be done in LabVIEW. E.g. make a LabVIEW dll and get pointers to it's functions and use it as callback.
This is a problem though:
+ It will take (a lot of) time and knowledge;
+ It will be hacky at best;
+ There might very well be a showstopper after investing a lot of time;
+ Even if it 'works', it could crash randomly, and will be hard to maintain.
Better options are to ask for an improved API, or make a wrapper API (in C\C++).
Even if you want to walk the LV route, you need a C++ compiler to try, change and debug the examples. Without working examples, making it work in LabVIEW will go from very hard to near impossible. You might as well make a wrapper.
12-09-2021 04:09 AM - edited 12-09-2021 04:13 AM
I agree with all you wrote Wiebe. I would just like to add that while I know you like to pose the possibility of creating a LabVIEW DLL to implement the Callback functionality, while this in theory can work, it is a very awful solution for all the reason you name and then several more.
It feels to me so wacky, unmaintainable, without any other merits than to show of that it can be indeed done, that anything else is absolutely bound to be a better solution, including to decide that it is too cumbersome to implement and drop the matter entirely.
My personal favorite always has been and still is, to write a LabVIEW friendly wrapper DLL in C that translates the C interface into something that LabVIEW can much more easily interface to. This requires some C programming knowledge but I can assure anyone that trying to do the same in LabVIEW to avoid having to write a DLL in C, requires even more C programming knowledge, including how a C compiler actually works when it translates your C code into machine code to put the data into memory and at what locations.
12-09-2021 04:29 AM - edited 12-09-2021 04:29 AM
@rolfk wrote:
I agree with all you wrote Wiebe. I would just like to add that while I know you like to pose the possibility of creating a LabVIEW DLL to implement the Callback functionality, while this in theory can work, it is a very awful solution for all the reason you name and then several more.
It feels to me so wacky, unmaintainable, without any other merits than to show of that it can be indeed done, that anything else is absolutely bound to be a better solution, including to decide that it is too cumbersome to implement and drop the matter entirely.
It works well and reliable for simple callbacks (like enumerators or some windows functions like SetWindowsHookExA).
This wouldn't be a simple callback though, and it would be just a small part of a huge puzzle.
As part of a professional solution, I agree that a wrapper would give the best result with the least effort.
12-09-2021 04:43 AM - edited 12-09-2021 04:47 AM
wiebe@CARYA wrote:
It works well and reliable for simple callbacks (like enumerators or some windows functions like SetWindowsHookExA).
Even that is potentially problematic, since the DLL is using whatever LabVIEW runtime version that corresponds to the LabVIEW version used to create the DLL while your LabVIEW VI may run a different LabVIEW version. It is not necessarily as bad anymore since around LabVIEW 2017 because you can build the DLL to be executed in newer LabVIEW versions if available but that has some other complications, with compiled code not working as expected if run in a different runtime than what it was compiled for.
If the DLL is not loaded into the actual LabVIEW kernel that your VI is using, then you can forget to easily pass data between the callback and your calling VI, since the user events, occurrence, queue, DVR and any other LabVIEW refnum based objects will not be the same in the callback VI and the calling VI.
The only way to guarantee that this is easily possible, is to recompile the callback DLL everytime in whatever LabVIEW version you want to actually use it. That is not a big problem if your callback is anyhow an integrated part of your overall project, but if you start to create reusable libraries, it gets a real annoyance.
12-09-2021 08:22 AM
@rolfk wrote:The only way to guarantee that this is easily possible, is to recompile the callback DLL everytime in whatever LabVIEW version you want to actually use it. That is not a big problem if your callback is anyhow an integrated part of your overall project, but if you start to create reusable libraries, it gets a real annoyance.
Agreed. I do see upsides in this downside:
- ) you have to compile this for the actual LabVIEW version.
+) you can compile this with just LabVIEW.
It's good to have options.
12-09-2021 12:40 PM
Hey All,
Thanks for the replies. I'm still a bit lost in all this to be honest. Ask for a better API? I don't even know what an API is.
Is LabVIEW still and option? What is my best professional answer here?
1.) Learning C well enough to write a wrapper to eventually use labview? (I don't know C at all)
2.) Learning C++ well enough to use the examples and write my own program that can be executed by a batch file via LabVIEW?
3.) Figuring out if a Red Pitaya can replace the DM35424 entirely and use python/Labview with a red pitaya.
4.) none of the above
I have attached a few jpegs of the call library function and the parameters for it.
The DM35424_Board_Open function is written in the DM35424_Board_access.h file as
DM35424LIB_API
int
DM35424_Board_Open(uint8_t dev_num, struct DM35424_Board_Descriptor ** handle);
It is an int and returns a value of 0 for success and -1 for failure. It requires two parameters. uint8_t dev_num which is going to be 0 and
struct DM35424_Board_descriptor ** handle which I'm not sure how to use.
The DM35424_Board_Descritpor is written in the DM35424_os.h file as
/**
@brief
DM35424 board descriptor. This structure holds information about
the board as a whole. It holds the file descriptor and ISR callback
function, if applicable.
*/
struct DM35424_Board_Descriptor
{
/**
A device-file handle used to communicate with the device via IOCTLs.
*/
HANDLE file_handle;
/**
Holds information about the board
*/
DM35424_Intrfc_Board_Info board_info;
/**
Holds handles for all the DMA callback threads.
*/
HANDLE dma_thread_handles[DM35424_MAX_FB][MAX_DMA_CHANNELS][MAX_DMA_BUFFERS];
/**
The termination events for the DMA callback threads.
*/
HANDLE dma_terminate_events[DM35424_MAX_FB][MAX_DMA_CHANNELS][MAX_DMA_BUFFERS];
/**
Holds handles for all the A/D interrupt callback threads.
*/
HANDLE int_thread_handles[DM35424_MAX_FB];
/**
The termination events for the A/D interrupt callback threads.
*/
HANDLE int_terminate_events[DM35424_MAX_FB];
} ;
Is there a way to make this work in LabVIEW?
It gets much worse when functions like DM35424_Open_ADC are going to be used as it requires even more struct parameters using handles.
Thanks for your replies.
James Detlefs
12-09-2021 02:09 PM
There are a few things which I find a problem with this API (Application Programming Interface) The boardHandle SHOULD not be exposed to the application. It should simply be an opaque pointer and it may be actually possible to treat it mostly as that.
But as we already established, this API is not well designed and interfacing to DLLs is always a lot of specialistic work. The fact that you don't even know what an API is, clearly shows that not only LabVIEW is new to you but programming in general. Trying to interface to a DLL in general with that amount of knowledge is really doomed and a DLL like this is even worse.
So maybe you should take a step back! What was the reason to select this hardware? Opportunity? Was it laying around? Did someone tell you it is the best thing since sliced bread? Or does it have some really unique capabilities that you absolutely and positively need? Because on your own you are never ever going to get this interface working in a reliable way. Even better designed DLL interfaces are a real challenge for most LabViEW programmers, even if they have quite a bit of general and LabVIEW programming experience. Your options are in my opinion:
1) Find someone who has real low level experience with interfacing DLLs and pay him to create an easy to use LabVIEW library to interface to this.
2) Reevaluate the hardware requirements and look for something that works for your requirements and comes with ready made LabVIEW drivers. The best in that respect are NI data acquisition boards. If you can find one that works for your requirements, you can pretty much forget about DLLs and C compilers and other low level details and concentrate on your actual problem.
3) Find another programming environment that is supported directly by the makers of your board. But it seems all they can support is C(++) and not in a good way.