LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

LabVIEW built DLL's in a multi-threaded C++ app, Help Needed!

Solved!
Go to solution
I'm working on a software application that is being developed primarily in C++. There is a component of this larger application, however, that is being developed in LabVIEW (for several reasons that I won't dive into here). This LabVIEW code is therefore executed within a C++ wrapper class that calls a LabVIEW built DLL.

All this was fine and dandy, untill we decided to multi-thread our C++ application for a performance increase. As a result, the LabVIEW DLL now gets called from multiple threads simultaneously. While this works functionally, it appears to create a bottleneck.  From all of my testing, it appears that resource locking is occuring such that only one thread has access to the .dll at a time. If I make the .vi used to define the dll function prototype as non-reentrant then this is what we see. As an example, say we have 3 threads all calling the same .dll method call. Thread 1, 2, and 3 all call the .dll within a few milliseconds of each other. Thread 1 completes the .dll call after X milliseconds. Thread 2 completes the .dll call after 2X milliseconds, and thread 3 completes after 3x milliseconds.

Now, changing the vi to reentrant, and running the same test, we see Thread 1, 2, and 3 all complete the .dll call in 3x milliseconds. While the fact that they now take the same amount of time to complete would lead you to believe they are happening in parallel, the fact that it takes 3x milliseconds as opposed to X milliseconds means they are not.

Has anyone ever dealt with these issues before? Is it possible that messing with the "execution system" for the vi will have an affect? What if different .dll methods but still attached to the same .dll are called from different threads? Same behavior? Is it a lost cause? Is there no way to make code within a single LabVIEW built .dll run in two different threads at the same? From what I understand this is easily doable with a normal (non-LV built) .dll.

Please, if anyone has any advice in this area, let me know!!!

Much appreciated,
Jesse Hurdus
0 Kudos
Message 1 of 11
(6,365 Views)

It appears that changing the vi execution priority to "subroutine" causes the timing to look much more like the dll code is in fact running in parallel on two separate threads.  Changing all of our dll code to "subroutine" execution priority is somewhat less than desireable though, as this has other side effects such as making the vi's never give up access to the thread until completion which would have other cascading effects.  As of right now though, this is the only way I can seem to get the same dll code to run in separate threads.

 

I was however, able, to produce the desired behavior by creating multiple different dll build specs that reference the same vi's as function prototypes, but rename the prototypes, essentially creating multiple dll's that all have the same code.  I could then hardcode in C++ to call a different dll based on the owning thread.  While this worked, it is obviously less desirable to have to produce a different dll, lib, and header file for every potential thread that might call our dll concurrently.

 

Please if anyone has any thoughts or experiences on the topic throw something out here!
0 Kudos
Message 2 of 11
(6,322 Views)

Hi jhurdus,

 

I've created a DLL out of LabVIEW code, and I'm calling this DLL multiple times within LabVIEW.  I've noticed that I have to enable two options in order to get the DLL code to run in parallel.

1) I have to set the VI that's being made into a DLL to be "reentrant".  (File » VI Properties » Execution » Reentrant execution: Preallocate close for each instance)

2) I have to enable each Call Library Function Node thread to "Run in any thread" instead of all of them being restricted to the UI (User Interface) thread.  (right-click on Call Library Function Node » Configure... » Thread » Run in any thread)

 

I'm not sure how to enable the "Run in any thread" option in C++, but I've outlined above how you can do this in LabVIEW.  I'll attach my LabVIEW project, code, and DLL for you to test further if you want.  (Since the code is in LabVIEW 8.6, I've also attached a screenshot if you have a different version of LabVIEW.)

 

Kevin S.

Applications Engineer

National Instruments

Download All
Message 3 of 11
(6,316 Views)

Keep in mind that you'll also need the LabVIEW 8.6 Run-Time Engine in order to run my DLL as well.

 

Kevin S.

Applications Engineer

National Instruments

0 Kudos
Message 4 of 11
(6,306 Views)
One thing to keep in mind too is that simply making the top level VI reentrant will not necessarily solve your issue.  Every subVI you call in your exported VI also needs to be reentrant (asuming it can be).  Otherwise your multiple threads will get to your DLL and then fight and wait for access to the subVIs.
0 Kudos
Message 5 of 11
(6,300 Views)

Kevin - I like the idea of replicating the test with a LV wrapper to try to locate the source of the problem!  Like you thought, I am running 8.5.1 so I wasn't able to run your example but it was easy enough to replicate.  I was also a little wary of your timing code, since your timers are started within the dll code itself.  If the locking is happening before callers actually get access to the dll code, then I wasn't sure your code would illustrate the problem.  Of course, if the vi took 3 seconds versus 9 seconds to run, it would've been obvious but to make sure i moved the timing measurement outside of the dll by putting each dll call in a timed sequence structure.  I've attached pictures of my testing vi so you can see how I set it up.

 

Anyway, long story short, you are correct!  I ran two tests, one where the dll vi was reentrant, one where it was non-reentrant.  In both tests the dll was set to "use any thread".  When the vi-was re-entrant, each sequence frame took approx 190 ms to complete.  Making the vi non-reentrant, sequence frame 1 took approx. 190 ms, sequence frame 2 took 2 x 190 ms, and sequence frame took 3 x 190 ms.

 

So what the question boils down to is.... how do you specify "run in any thread" when calling the .dll from C++!!!!!  I'm not sure on the internal working of the Call Library Node, but could it be sending some input/flag to the dll that is not defined in the dll header file to specify this behavior?  Or is it higher level than that?  In our c++ code, we are calling the .dll from separate threads, it seems like that is the most logical thing "run in any thread" vs "run in ui thread" would do, that is call the dll functions separately from multiple threads, rather than queue the calls up in a single thread and make them one at a time.

 

It would be awesome to hear from NI R&D on what's going on underneath the hood here.

 

Matthew - Good point.  Yes, we are aware of that issue.  If we can at least enter the .dll in parallel however, we can minimize the resource locking to places only where it has to (i.e. shared memory access) and we can let other things (long array searches that eat up the majority of the execution time) happen in parallel.  For these tests though, there are no common sub-vi's, reentrant or non-reentrant.

Download All
0 Kudos
Message 6 of 11
(6,281 Views)

Hi jhurdus,

 

I've been in touch with R&D, and there don't seem to be any secret flags or options needing to be set in order to run multiple instance of a LabVIEW DLL simultaneously.  R&D commented that your DLL may not be fully threadsafe (or specified as reentrant) and/or that you code may be architected in a way such that resources are shared.  It sounds like from your previous post that you've taken much of this into account though.

 

I'll post more if I find additional details.

 

Does your DLL access hardware at all?  What kinds of things do you do in your DLL?

 

Kevin S.

Applications Engineer

National Instruments

0 Kudos
Message 7 of 11
(6,207 Views)

Hey Kevin,

 

I've attached a .zip file with my project and dll VI so that you can verify that the vi does not involve any hardware and has no shared resources (that I am aware of).  Any word on what exactly happens in the call library node when "run in any thread" is specified?  Does that just mean that LV is making the dll call from separate threads?  Knowing what happens in that case would make it easier to try and replicate in c++ to verify that it is possible.

 

Thanks again,

Jesse

0 Kudos
Message 8 of 11
(6,202 Views)

jhurdus wrote:

Hey Kevin,

 

I've attached a .zip file with my project and dll VI so that you can verify that the vi does not involve any hardware and has no shared resources (that I am aware of).  Any word on what exactly happens in the call library node when "run in any thread" is specified?  Does that just mean that LV is making the dll call from separate threads?  Knowing what happens in that case would make it easier to try and replicate in c++ to verify that it is possible.

 

Thanks again,

Jesse


I don't see your DLL_wait.vi being set to be executed as reentrant. Also the project file to build the lockingtest.dll seems not complete: There is no exported function defined.

 

 

Rolf Kalbermatter

Rolf Kalbermatter
My Blog
0 Kudos
Message 9 of 11
(6,186 Views)
Solution
Accepted by jhurdus

Hi jhurdus,

 

I know you're working with an NI engineer with this issue, but I still wanted to post an answer to one of your last questions.

 

When using the Call Library Function Node in LabVIEW, there is the option to select "Run in UI thread" or "Run in any thread".  This option, according to the LabVIEW help file (Call Library Function Dialog Box), enables the developer to switch the Call Library Function Node thread from the user interface thread (default setting) to the thread from which the VI is currently executing.  Additional details of when you would want to switch the execution thread are in the help file.

 

Kevin S.

Applications Engineer

National Instruments

Message 10 of 11
(6,139 Views)