LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

DLL access violation - unless caller's FP is open.

Solved!
Go to solution

rolfk, I certainly would not ask you to rewrite the DLL as a favor!

Perhaps you can help solve the current issue - given a bit more information(?)

I'm pretty sure the error occurs in "PerformCollectThread" when the "ghStartDAQ" event-wait times-out - at which point the logic merely loops to wait again.

There is almost always a delay (before error) after loading second DLL, but the delay has always been less than 10sec - the timeout value of the event-wait.

 

Using Procmon, I was able to associate the "violation" "EIP address" to the image-space of the first DLL.  I modified the second DLL to do nothing (return immediately) on call to DLL_INIT.  At seems merely loading the second DLL allocates some memory that the first DLL tries to access on timeout of "ghStartDAQ" event.

 

When I originally posted this question, it was not clear (to me) whether LabVIEW might have a roll in the cause of the error.  Now, having built a  simple test-case that only calls the DLLS ("DLL_Init"), it seems likely that the error is related to improper CPP coding.  Still, if you have the experience to help solve this problem, your help would be appreciated!

0 Kudos
Message 11 of 15
(2,102 Views)

@550nm wrote:

 

 

When I originally posted this question, it was not clear (to me) whether LabVIEW might have a roll in the cause of the error.  Now, having built a  simple test-case that only calls the DLLS ("DLL_Init"), it seems likely that the error is related to improper CPP coding.  Still, if you have the experience to help solve this problem, your help would be appreciated!


In my extensive development work with C DLLs in the last 25 years, whenever it crashed it was related to my C code in the DLL and NEVER EVER to LabVIEW! Maybe I'm a bad C programmer but when incorporating DLLs into LabVIEW the chance that any such error is related to that DLL rather than LabVIEW is almost 100%, with an infinitesimal small chance that LabVIEW does something bad.

 

And yes there existed bugs in parts inside LabVIEW that could crash your application. From the top of the head I can remember two particular ones: Heavy bidirectional TCP/IP operations through the LabVIEW nodes could somewhere around LabVIEW 6 or 7 run into race conditions that would crash your application. More recent there was a bug in the protection of queues and other synchronization objects that offered a very small time window (a few nanoseconds only) in which a reader could access the queue while its state was modified by the writer side. It was not likely to be hit unless you had a very heavy use of that object but it could happen. None of these involved however any use of an external DLL, it all was fully reproducable in pure LabVIEW and adding (properly) working external DLLs would rather have minimized the chance to trigger this bug as there would be likely more to do in the app than just sit there and hit the queue as fast as possible with accesses.

 

Your guess that it is related to the PerformCollectThread() function was also my first hunch. But I can't see at a first glance anything that would have cross DLL influences. The gbStartDAQ event is a nameless event so can't be referenced by anyone who doesn't have the actual event handle.

 

There are various questionable things in that code such as the call to timeEndPeriod() just before terminating the PerformCollectThread() function without a matching timeStartPeriod() anywhere I can see, or the ghStartDAQ event handle that never gets closed properly and I believe there are other such resources that are not guaranteed to be cleaned up properly, but that should not cause a crash, only handle leaks that could add up over subsequent cycles of DLL_Init() and DLL_Close().

 

Or what to think about code constructs like this:

    CollectThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PerformCollectThread,
						 (LPVOID)NULL, 0, &CollectThreadID);
    SetThreadPriority(CollectThread, THREAD_PRIORITY_HIGHEST);
    if  (CollectThread == NULL)
      retval = -1;
    else
      bDLLOpened    = TRUE;

where the validity of the thread handle is only checked after an attempt to set the thread priority.

Rolf Kalbermatter
My Blog
0 Kudos
Message 12 of 15
(2,096 Views)

rolfk, I too noticed the odd/unmatched TimeEndPeriod but missed the belated null-thread check.

The DLL acts as though the thread is, in fact, not null, and the unmatched syntax executes if the DLL terminates normally - which it hasn't (when access violation occurs). Based on the delay before error (after DLL2 call) I'm certain the problem is either:

- proper termination of call to WaitForSingleObject, or

- invocation of subsequent call to same function.

 

I'm suspecting some weird thread-switching subtlety.  I have not seen the bug when - in my test case - raw CLF nodes are used to run DLL_Init. Normally all the CLF nodes are implemented in a single Functional-Global Variable, with an enum determining which function is called.  I can run the test-case in two modes, either calling raw CLF nodes or using the FGV.  The bug has never occurred when calling raw CLF nodes.  According to here, LabVIEW automatically switches threads unless CLF is flagged as "run in any thread".  Also, the CLF caller is not supposed to run in User Interface thread.  (Of course I've experimented with these parameters.)

 

There's also a caveat related to (C++) WaitForSingleObject; apparently the gbStartDAQ handle is supposed to have a "SYNCHRONIZE" attribute enabled (haven't quite figured out how to do that).

I also notice a new failure mode where DLL#2 is blamed for the error!

Sometimes the OS thinks DLL#1 is to blame, other times DLL#2 (remember, DLL#2 returns immediately on call to DLL_Init!(?)

0 Kudos
Message 13 of 15
(2,084 Views)
Solution
Accepted by topic author 550nm

It seems that the CLF node can unload a DLL from memory, and that's what was happening - causing LabVIEW to crash on call to second DLL. According to the CLF help, the DLL only unloads the previous DLL if an empty/invalid path is wired, but it _behaves_ as if wiring a _different_ path causes the previous DLL to be unloaded.  This would explain why my test program never crashes when two different CLF nodes are used to launch my two DLLs.  When using the CLF node in an FGV (passing DLL path as parameter), the _same_ CLF node was calling both DLLs, unintentionally unloading the first DLL (which was still running a thread) and crashing LabVIEW.

Making the FGV reentrant solves the problem - effectively creating unique instances of the CLF node (one in each cloned FGV).

0 Kudos
Message 14 of 15
(2,056 Views)

You misunderstood the help text. This is not meant to imply that this is the only way a DLL gets unloaded in the Call Library Node but that it is THE way to cause a CLN to unload the DLL if you should want to do so. And the unload only really happens if there is no other CLN anywhere in your whole application that has the same DLL also loaded.

 

Basically every instance of the CLN can at any time only link to one DLL and function. If you pass in a different DLL name (including an empty/invalid path) it has to release the previous DLL and if this happens to have been the last CLN instance in your application that referenced that DLL (and there is no other code in your application including other DLLs that may reference a function in your DLL), Windows will effectively unload the DLL from the process image. Setting your VI to be reentrant is one solution as then each VI is instantiated with its own dataspace and maintains its own instance copy of the CLN. Other solutions would have been to create two distinctively named VIs where each links to its own copy of your DLL "instance", or what would also have worked is to have somewhere in your application two other CLNs that both link explicitly to the different DLL copies, even if you never call them (caveat if you put them in a disable structure or in a case structure wired with a boolean constant on the diagram LabVIEW will optimize them away when you build the application and the effect gets lost). Because these two nodes reference your DLLs, their reference count will never reach 0 and therefore the release/unload call in the CLN that is passed alternating DLL paths will not have any real effect. But the continuous release/unrelease cycle for the DLLs will of course require additional CPU time and is kind of a stress test for the Windows LoadLibrary()/FreeLibrary() functions.

 

And this points out again how important it would be to actually post the VIs or at least the diagram snippet you have been working with when asking for help on such a forum. When you said you had copied the DLLs to create two devices I was automatically assuming that you had also made copies of the VI containing the Call Library Node for each of the DLLs. Never would I have expected that you used the dynamic path feature of the Call Library Node to call the two DLLs alternatingly through the same CLN. If that had been clear to me I could have pointed out the flaw in your programming right away.

Rolf Kalbermatter
My Blog
0 Kudos
Message 15 of 15
(2,044 Views)