kawait Posted May 7, 2008 Report Share Posted May 7, 2008 Hi everyone, With the help of rolfk and others I successfully create a VI and a wrapper DLL that do what I want. But there is one problem that is annoying me: The VI CRASHES quite frequently after the first run in LabVIEW 8.5. I know the most possible cause is that there are some access violations because I am not careful in using pointers. Although I have tried my best to avoid that, I am not a very experienced programmer so I may make some mistakes that I am not even aware of. There are some questions that may help identify the problem. 1. My wrapping DLL does have a pair of functions that performs initialization and cleanup, respectively. The initialization function loads required DLLs and allocates memory for all LV data type s(string handle, int...etc) that is used in user LV event. The cleanup function does that necessary cleanup (Free DLLs, release LV data types..etc). Initially, I call the functions both explicitly in my VI, as well as in the PROCESS_ATTACH and PROCESS_DETACH section in the add_main. Is it a good way to do that? Or I should just rely either the DLL_Main or explicit call of the functions? 2. Should I write the function prototype in my DLL source code EXACTLY as displayed in the call library function node in LabVIEW? Which means using data types defined in extcode.h instead of C/C++ primitive data types (int, long, double...etc). Or it doesn't really matter as long as I know they are interchangeable? 3. For variables that are the outputs of the DLL I pass them as pointers to the wrapper DLL. In the LabVIEW VI do I have to pass something like this? In the above diagram the forth argument is the string output of a DLL (with maximum length of 100). I am not sure if I am correct if I do that. Should I pass a array control instead? Or should I not pass anything as the input and instead allocate memory for the data type using LabVIEW Memory Manager functions like DSNewPtr? 4. I found that after I build my VI as an executable and run it, it crashes MUCH LESS. (Although it still after several run cycles). Does it mean that LabVIEW 8.5 is not very stable compared to older versions of LabVIEW? (I haven't try my program in older versions of LabVIEW though) Finally, if you have any advise regarding crashes in DLL-calling VIs (pitfalls, good practices, dos and donts...etc), plz share with me. Coz your experience would probably save my life a lot Thanks in advance! Quote Link to comment
Rolf Kalbermatter Posted May 7, 2008 Report Share Posted May 7, 2008 QUOTE (kawait @ May 5 2008, 09:17 PM) Hi everyone, With the help of rolfk and others I successfully create a VI and a wrapper DLL that do what I want. But there is one problem that is annoying me: The VI CRASHES quite frequently after the first run in LabVIEW 8.5. I know the most possible cause is that there are some access violations because I am not careful in using pointers. Although I have tried my best to avoid that, I am not a very experienced programmer so I may make some mistakes that I am not even aware of. There are some questions that may help identify the problem. 1. My wrapping DLL does have a pair of functions that performs initialization and cleanup, respectively. The initialization function loads required DLLs and allocates memory for all LV data type s(string handle, int...etc) that is used in user LV event. The cleanup function does that necessary cleanup (Free DLLs, release LV data types..etc). Initially, I call the functions both explicitly in my VI, as well as in the PROCESS_ATTACH and PROCESS_DETACH section in the add_main. Is it a good way to do that? Or I should just rely either the DLL_Main or explicit call of the functions? 2. Should I write the function prototype in my DLL source code EXACTLY as displayed in the call library function node in LabVIEW? Which means using data types defined in extcode.h instead of C/C++ primitive data types (int, long, double...etc). Or it doesn't really matter as long as I know they are interchangeable? 3. For variables that are the outputs of the DLL I pass them as pointers to the wrapper DLL. In the LabVIEW VI do I have to pass something like this? http://lavag.org/old_files/monthly_05_2008/post-3626-1210038859.jpg' target="_blank"> In the above diagram the forth argument is the string output of a DLL (with maximum length of 100). I am not sure if I am correct if I do that. Should I pass a array control instead? Or should I not pass anything as the input and instead allocate memory for the data type using LabVIEW Memory Manager functions like DSNewPtr? 4. I found that after I build my VI as an executable and run it, it crashes MUCH LESS. (Although it still after several run cycles). Does it mean that LabVIEW 8.5 is not very stable compared to older versions of LabVIEW? (I haven't try my program in older versions of LabVIEW though) Finally, if you have any advise regarding crashes in DLL-calling VIs (pitfalls, good practices, dos and donts...etc), plz share with me. Coz your experience would probably save my life a lot Thanks in advance! Lot's of questions and little hard facts to check and look at. A few comments though: 1) What I usually do is having one or more exported function to allocate resources (my DLL typically can handle multiple connections/resources) and also according functions to release those resources. All resources are also stored in a global list and on DLL_PROCESS_DETACH I check this list and deallocate anything that might still be in there. I normally never do resource allocations in DLL_ROCESS_ATTACH but instead make sure that the functions do check and if necessary allocate whatever they need at runtime only. 2) If you are positive that the types are compatible it really doesn't matter. Those nice extcode.h datatypes are for clarity for the programmer and to allow LabVIEW to define them to whatever a specific compiler may need to but in nowadays 32bit only world these are fairly consistent across compilers and plattforms. 3) Not really clear to me. But of course for parameters that need to return something to the caller you always need to pass them by reference (as pointer). You can also convert the Byte Array to a string and pass it as C string pointer directly. This has the added bonus that LabVIEW will search for the NULL terminating character on return and pass only the valid part of the string further. Never ever use DSNewPtr or any of the other *Ptr memory manager functions for DLL functions parameters that are passed from LabVIEW or returned to LabVIEW. Those parameters are either skalars, C pointers or LabVIEW data handles. For skalars there is nothing special, for pointers they have to be allocated in the caller (e.g. Initialize Array). The only parameters passed from and to LabVIEW that can be modified in terms of memory allocation are handles and for that you will need the according handle functions of the memory manager. Although for most cases I really would recommend to use NumericArrayResize whenever possible. 4) No! LabVIEW is definitly not more or less stable in calling external code routines in just about any version since 5.x or so. What you see is that in an executable the memory layout of your application is of course different so when your wrongly configured function parameters or your wrapper DLL happen to step on invalid memory (invalid in the sense that they shouldn't try to write to it nor even access it at all) they will overwrite sometimes vital LabVIEW runtime data and sometimes just some LabVIEW edit data. If overwriting LabVIEW runtime data, this sooner or later will cause big trouble. It could crash as early as when doing the writing or as late as when exiting the application since LabVIEW tries to deallocate resources that have been invalidated by the overwriting. Overwriting LabVIEW edit data or such will possibly only be detected when you happen to open for instance a front panel or diagram and try to do some modifications. It could be just some strange text somewhere or weird attributes of objects or LabVIEW could be tripping then over the non-sensical data and go belly up at that point. Rolf Kalbermatter Quote Link to comment
Sebastian Posted May 7, 2008 Report Share Posted May 7, 2008 I have done the same in my master thesis project and found a solution! Use a "array to cluster" and set manualy the cluster size by a right click to the function block. Than adapt the function call to "adapt to type" and pass the pointer or handle! /Sebastian Quote Link to comment
kawait Posted May 8, 2008 Author Report Share Posted May 8, 2008 QUOTE (rolfk @ May 6 2008, 04:26 PM) Lot's of questions and little hard facts to check and look at. A few comments though:1) What I usually do is having one or more exported function to allocate resources (my DLL typically can handle multiple connections/resources) and also according functions to release those resources. All resources are also stored in a global list and on DLL_PROCESS_DETACH I check this list and deallocate anything that might still be in there. I normally never do resource allocations in DLL_ROCESS_ATTACH but instead make sure that the functions do check and if necessary allocate whatever they need at runtime only. 2) If you are positive that the types are compatible it really doesn't matter. Those nice extcode.h datatypes are for clarity for the programmer and to allow LabVIEW to define them to whatever a specific compiler may need to but in nowadays 32bit only world these are fairly consistent across compilers and plattforms. 3) Not really clear to me. But of course for parameters that need to return something to the caller you always need to pass them by reference (as pointer). You can also convert the Byte Array to a string and pass it as C string pointer directly. This has the added bonus that LabVIEW will search for the NULL terminating character on return and pass only the valid part of the string further. Never ever use DSNewPtr or any of the other *Ptr memory manager functions for DLL functions parameters that are passed from LabVIEW or returned to LabVIEW. Those parameters are either skalars, C pointers or LabVIEW data handles. For skalars there is nothing special, for pointers they have to be allocated in the caller (e.g. Initialize Array). The only parameters passed from and to LabVIEW that can be modified in terms of memory allocation are handles and for that you will need the according handle functions of the memory manager. Although for most cases I really would recommend to use NumericArrayResize whenever possible. 4) No! LabVIEW is definitly not more or less stable in calling external code routines in just about any version since 5.x or so. What you see is that in an executable the memory layout of your application is of course different so when your wrongly configured function parameters or your wrapper DLL happen to step on invalid memory (invalid in the sense that they shouldn't try to write to it nor even access it at all) they will overwrite sometimes vital LabVIEW runtime data and sometimes just some LabVIEW edit data. If overwriting LabVIEW runtime data, this sooner or later will cause big trouble. It could crash as early as when doing the writing or as late as when exiting the application since LabVIEW tries to deallocate resources that have been invalidated by the overwriting. Overwriting LabVIEW edit data or such will possibly only be detected when you happen to open for instance a front panel or diagram and try to do some modifications. It could be just some strange text somewhere or weird attributes of objects or LabVIEW could be tripping then over the non-sensical data and go belly up at that point. Rolf Kalbermatter Thanks for your prompt reply again! Rolf :worship: In the diagram in question 3, the called DLL function will do something and pass the result as a string (which is the forth argument, defined as char* in C function prototype). The reason of initializing an 100-element empty array and passing it as a pointer to the call library function node is that I want to allocate enough space for the string output and tell the node "Hey, I have prepared this chunk of memory space for the string output". I used unsigned int8 just because it can be directly casted to a char array. What I am not sure is if LabVIEW handles that automatically for me so that I can only just wire the output and as long as LabVIEW see the output is wired, it will allocate enough memory so I dont need to pass the empty array as the input. BTW, after I made my post here, I seemed to find out some big mistake. The explicitly call the cleanup function in my VI seemed the one which causes trouble. After I have removed the explicit call to the cleanup function my VI doesn't crash at all after almost 10 run cycles (I cant say that it wont crach though). I know it is just because I have done the cleanup of some resource too early in my DLL cleanup function and I will take further look to that. QUOTE (Sebastian @ May 6 2008, 07:00 PM) I have done the same in my master thesis project and found a solution! Use a "array to cluster" and set manualy the cluster size by a right click to the function block. Than adapt the function call to "adapt to type" and pass the pointer or handle! /Sebastian Thanks for you idea! it seems useful if I want to pass clusters to DLL functions which take structs as arguments. Quote Link to comment
Rolf Kalbermatter Posted May 8, 2008 Report Share Posted May 8, 2008 QUOTE (kawait @ May 6 2008, 10:05 PM) Thanks for your prompt reply again! Rolf :worship: In the diagram in question 3, the called DLL function will do something and pass the result as a string (which is the forth argument, defined as char* in C function prototype). The reason of initializing an 100-element empty array and passing it as a pointer to the call library function node is that I want to allocate enough space for the string output and tell the node "Hey, I have prepared this chunk of memory space for the string output". I used unsigned int8 just because it can be directly casted to a char array. What I am not sure is if LabVIEW handles that automatically for me so that I can only just wire the output and as long as LabVIEW see the output is wired, it will allocate enough memory so I dont need to pass the empty array as the input. Well you always have to allocate the space in the caller for C type pointers. Nothing wrong with that. In principle there is also no difference in passing a byte array or a string. Memory wise they are the same. But the way you do it now you will receive a byte array from the dll that is exactly 100 bytes long independant of how many characters the DLL filled in. The Byte Array to String function will not change anything about that so you always end up with a string of 100 characters with the desired characters in the beginning and NULL characters in the rest of the string. The Call library Node does however have special treatment for parameters that are configured to be C style string pointers. On return LabVIEW will scan the string and search for the terminating NULL character and then resize the string to only be of that size. So by moving the Byte Array to String function before the Call Library Node you do both allocate the size of it (it's a lot easier to allocate a Byte Array using Initialize Array than putting a string constant on the diagram that contains the necessary amount of spaces or whatever) and on return you actually already get the string properly resized to the real string. Please note that this resizing will only shrink the string buffer. You do need to make sure that you pass in an array that is positively and under any circumstances big enough to receive the actual string. QUOTE BTW, after I made my post here, I seemed to find out some big mistake. The explicitly call the cleanup function in my VI seemed the one which causes trouble. After I have removed the explicit call to the cleanup function my VI doesn't crash at all after almost 10 run cycles (I cant say that it wont crach though). I know it is just because I have done the cleanup of some resource too early in my DLL cleanup function and I will take further look to that. That is the difficulty! No crash does not mean it is already working perfect. It may still just destroy non-vital data that could only show up as crash when you end LabVIEW, since it tries to dealocate resources you happen to have destroyed or it may be as subtle as only visual or non-visual corruptions to your actual VIs that may trigger many days later. Rolf Kalbermatter Quote Link to comment
kawait Posted May 10, 2008 Author Report Share Posted May 10, 2008 QUOTE (rolfk @ May 7 2008, 02:26 PM) Well you always have to allocate the space in the caller for C type pointers. Nothing wrong with that. In principle there is also no difference in passing a byte array or a string. Memory wise they are the same. But the way you do it now you will receive a byte array from the dll that is exactly 100 bytes long independant of how many characters the DLL filled in. The Byte Array to String function will not change anything about that so you always end up with a string of 100 characters with the desired characters in the beginning and NULL characters in the rest of the string.The Call library Node does however have special treatment for parameters that are configured to be C style string pointers. On return LabVIEW will scan the string and search for the terminating NULL character and then resize the string to only be of that size. So by moving the Byte Array to String function before the Call Library Node you do both allocate the size of it (it's a lot easier to allocate a Byte Array using Initialize Array than putting a string constant on the diagram that contains the necessary amount of spaces or whatever) and on return you actually already get the string properly resized to the real string. Please note that this resizing will only shrink the string buffer. You do need to make sure that you pass in an array that is positively and under any circumstances big enough to receive the actual string. That is the difficulty! No crash does not mean it is already working perfect. It may still just destroy non-vital data that could only show up as crash when you end LabVIEW, since it tries to dealocate resources you happen to have destroyed or it may be as subtle as only visual or non-visual corruptions to your actual VIs that may trigger many days later. Rolf Kalbermatter Thanks you again Rolf!. :worship: After I have carefully checked and modified the VI and C++ code, the VI now doesn't crash even I close it after many run cycles. Then I go ahead and write a small VI to test another DLL and get a trouble! My test VI run fine but everytime I close the VI, my LabVIEW halted...I have to force it to close it using Windows Task Manager. When I examined my VI and tried to figure out the problem, I found that it is possible to raise the error checking level of each call library function node. I think it is always a good idea to have maximum error checking during development and change the level to maximum for all the node. And I got the following error every node in my VI... I went back and checked my previous VI, and I also got the same error after I set the error checking level to maximum....With default error checking, they are all run fine. The error only happens when I set the error checking level to maximum. At this point, I believe that I have made some big mistakes that I am still not aware of. The error message suggests that there is a mismatch in calling conventions, and I am using a macro for dll export like this: #ifdef A_DLL_EXPORTS #define DLL_API __declspec(dllexport) #else #define DLL_API __declspec(dllimport) #endif DLL_API int ThisIsDllFunction(); And I setup all the call library node to use stdcall calling convention. Anyone have thought about this error? QUOTE (Sebastian @ May 6 2008, 07:00 PM) I have done the same in my master thesis project and found a solution! Use a "array to cluster" and set manualy the cluster size by a right click to the function block. Than adapt the function call to "adapt to type" and pass the pointer or handle! /Sebastian Thank you for your info. I' ve never thought of it as one of the possible solutions. Quote Link to comment
Rolf Kalbermatter Posted May 14, 2008 Report Share Posted May 14, 2008 QUOTE (kawait @ May 9 2008, 05:04 AM) #ifdef A_DLL_EXPORTS #define DLL_API __declspec(dllexport) #else #define DLL_API __declspec(dllimport) #endif DLL_API int ThisIsDllFunction(); Unfortunately this does not specify the calling convention! You would need something like __stdcall in those two declarations too. If you are using Visual C however you are most likely using stdcall already since this is the default calling convention used by 32Bit Microsoft C compilers. To be sure check in the compile conficuration for your project or files what the default calling convention is. Rolf Kalbermatter Quote Link to comment
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.