02-10-2018 02:51 PM - edited 02-10-2018 02:58 PM
wrote:So the reason Labview doesn't report the available error code is to prevent me from downloading malicious software? You can't seriously expect me to believe this.
That reasoning is ridiculous and you know that! What I tried to explain is that error codes may be helpful to the developer but are in almost all cases useless or even harmful for the end user. You fall in this situation a bit in a grey area as you are both the end user of the LabVIEW software as well as the developer of the DLL, who might have a little bit more understanding about what an error code means, but in general reporting error codes to an end user is simply BAD!
And don't go and say that this is bs since allmost all the LabVIEW functions return error codes too. If you program in LabVIEW and use the programming nodes you are not the end user but the programmer and then you do want to have some form of runtime error handling. Calling a DLL however is a bit different. The Windows API is clearly designed to be concise and while the actual developer of the VI that calls the DLL might be interested to get more details about the failure, the Windows API doesn't provide it consistently across different versions of Windows. LabVIEW can not improve that behavior without ridiculous amounts of work. And while there are many things in LabVIEW that would be nice if they were more elaborate, I rather have the developers spend time on real features than improving on a niche area that most LabVIEW users never will and should be bothered to try out. DLL interfacing is an advanced topic and someone attempting to do that can be expected to have some serious C programming experience. In fact trying to do it without this experience is simply a disaster waiting to happen. Calling a DLL is not suddenly easier because you do it in LabVIEW but remains the same difficult task as when you do it in C, including having to make sure you actually use the correct datatypes and allocate all the memory buffers properly as you have to do it in C.
This is precisely what the LabView documentation example suggests: creating and compiling a simple dll example in some random folder on your computer. There is no fancy "installer" involved. So either you are wrong, or NI is promoting poor development practices.
Well, using your reasoning logic from above, the solution for NI is of course to remove all those examples altogether and be done with the complaints :-).
In reality NI is not in the business of teaching you to develop DLLs. The examples are a means to an end to get a DLL that can be called from LabVIEW to demonstrate how to use the Call Library Node, but not even a crash course about how to develop a DLL.
As such the DLL part is quick and dirty and the absolute minimum to get a working DLL that can be called. It is by no means a recommended practice about DLL development. The assumption is that someone who knows what he is doing about DLL development, wants to call his DLL from LabVIEW, and to get a quick sample that shows how this can be done. The created DLL is functionally useless and absolutely not a recommended development practice!
And many of those examples come either originally from the time of Visual C 6.0 which did only have the standard Windows MSVCRT.DLL C runtime dependency or at least assume that you use Microsoft Visual C. Since most programs on your computer are compiled with some version of Visual C, chances are pretty great that the necessary C runtime library for this version is already installed properly on the system. Using a less common compiler like GCC is almost certain to not have the necessary runtime support already present on a target computer and even on your MSys installed computer those libraries might not be by default installed in a common area but only inside the environment that the C compiler creates when you start it up, (for instance by running a startup script that adds the necessary directories to the PATH variable before the actual C compiler environment is started). Most likely somewhere in the documentation for your compiler it states exactly what sort of dependencies that are, usually they also vary depending on if you use certain extra compile switches such as compiling with and without multithreading support, etc. and how to get them properly installed on a target system to make your program executable. Of course nobody reads documentation, but if a user fails to do that it is certainly not the fault of LabVIEW. And if it is not documented then honestly, the entire C compiler toolchain is simply crap and it's again not LabVIEW's fault.
This can't be entirely true, because I can create a program which has no problem calling the dll which labview fails to load.
Yes you can create a program and use your DLL from it, because you use the same C compiler and on the same computer that this C compiler is installed on. Your C compiler/linker might even go to lengths to copy any necessary runtime dependency into the executable folder for your test executable or do some other voodoo magic in the background to allow your executable to startup with the correct runtime support visible to Windows. And since your DLL was created with the same C compiler and gets loaded in the process space of your test executable, Windows will have no problems to locate the runtime dependencies, which got already loaded into memory when the main executable got loaded. And most likely your test executable does not even load the DLL explicitly with LoadLibrary() but you simply link into your executable the export lib created by the C compiler for your DLL. That import lib may try to do some voodoo magic of its own to resolve possible dependencies if the calling executable was created in the same C compiler version than the DLL.
This kind of testing with some test executables created for the pure purpose of testing your DLL is fundamental to debugging your DLL and make sure that it works properly but by no means enough to guarantee that it can be called from other environments than this test example and on other computers than your development machine.
The next step is to run your test executable on another computer and get it to load and use your DLL from there. That way you can figure out what type of runtime dependencies your executable and DLL might have. This is still not sufficient though, as you also need to test that it can be called from something else than an executable created with the same C compiler as the DLL itself. But if you have come this far you know at least what extra runtime libraries you might need to install on a target computer. And honestly if you are developing such a DLL, testing on one single other computer is by far not enough. You need to test it on maybe a dozen computers with varying versions of the OS before you can be reasonably sure that most dependency problems are properly figured out. Up to this point LabVIEW hasn't even been in the picture at all!
Now you can start to test it with LabVIEW. And since you put so much value in the error code that LoadLibrary() reports, I sat down for 15 minutes and did a quick and dirty VI for you that you can use to point at a DLL and let it try to load it into memory and that returns that exact error code if the loading fails. Good luck with figuring out the problem from this error code. It will in most cases simply confirm what LabVIEW already told you: Windows was not able to load the library and except for obvious errors like pointing it at a non DLL file it simply means that either the DLL load routine DLLMain() itself failed or that Windows couldn't find some dependency of your DLL.
NOTE: Do not change the subroutine setting of the subVI. This is essential to make sure that the GetLastError() call gets to see the actual error code that LoadLibrary() produced. Without subroutine setting, there is no guarantee that GetLastError() is called in the same thread than LoadLibrary() nor in direct sequential order. Normal priority VIs can and will yield control of the current thread regularly back to LabVIEW so LabVIEW can sort of multitask other waiting code clumps. This is in addition to the OS backed multitasking that LabVIEW uses since version 5.0 but this cooperative multitasking was in LabVIEW already in version 2.0 and still is there too.
Another note: I did originally just add the returned error code into the error cluster. But that results in weird error messages from the LabVIEW error handler since the error codes from Windows have not the same meaning than the LabVIEW error codes. So I added an extra indicator that displays the error code directly instead.
DLL Tester is the main VI that allows you to start the VI with the run arrow. The WIN Load Library.vi can't be started directly since it is set as subroutine priority.
04-13-2018 02:41 AM - edited 04-13-2018 02:42 AM
Thanks for very usefull examples in this thread. They helped me to build a wrapper dll for some inconvenient library from Testo IR API.
But there I've encountered a problem: when this wrapper DLL is called from my Labview VI - it causes the Labview 1097 error. When called from a simple C++ programm - works normally. Moreover, if I comment out (in this wrapper DLL) the call for IR API function - the Labview VI works.
What could be the cause for this Labview crash? What could be so special in that ThermalImageApi.dll function from Testo IR API?
04-13-2018 02:46 AM - edited 04-13-2018 02:50 AM
Do you see the wchar_t type for the fname parameter?
wchar_t is C speak for a wide char string. LabVIEW strings however are ANSII strings.
What happens is that the DLL tries to interpret this string and for that it has to find the end of the string. The ANSI string contains of a bunch of bytes with a single NULL byte at the end. The path interpretation expects a bunch of words with a NULL word at the end. Interpreting the bytes that you pass in as words not only gives nonsense characters in the UTF16 alphabet that widechars are under Windows but it most likely won't see the NULL word for a long time after the actual string, scanning into memory that it has absolutely no business to touch.
04-13-2018 03:09 AM
That is why I've built the wrapper DLL (see the attached open_image.cpp file in ZIP in previous post) - it converts the normal Labview string (transfered as a C (Null-terminated) string into wchar_t, and gives it to IR API function.
The wrapper DLL works when called from a simple C++ application (attached here) - so the function arguments seem to be transferred correctly.
04-13-2018 04:57 AM
And what is your default calling convention in your project?
Maybe add an explicit __cdecl to the function prototype?
04-13-2018 05:09 AM
In fact it looks like this:
extern "C" { __declspec(dllexport) int open_image(int *id, char *fname); }
and the function body:
int open_image(int *id, char *fname) { std::string str(fname); std::wstring wstr(str.begin(), str.end()); //TESTO_IRAPI_RESULT tresult = testo_irimage_open(id, wstr.c_str()); TESTO_IRAPI_RESULT tresult = testo_irimage_open(id, (const wchar_t*)(L"c:\\work\\IR000000.bmt")); return tresult; }
std::wstring.c_str() returns const wchar_t*.
But even in the way that is shown in this code (just (const wchar_t*)(L"c:\\work\\IR000000.bmt")) the Labview calling dll crashes with error 1097 - it's a mistery for me. Nevertheless this very wrapper DLL works when called from a simple C++ application.
04-13-2018 05:15 AM
Yes what is your calling convention in your project?? You configured the Call Library Node to use cdecl!
If you compile your DLL and your test program with the same calling convention configuration in the C project then it will of course work! But LabVIEW knows nothing about your C project settings!
04-13-2018 05:16 AM
Here is the test application code:
extern "C" { __declspec(dllexport) int open_image(int *id, char *fname); } int main(int argc, char** argv) { int id, result; std::cout << "IR library test" << std::endl; if (argc == 1) { char *fname = "c:\\work\\IR000000.BMT"; result = open_image(&id, fname); } else { result = open_image(&id, argv[1]); } std::cout << "Result: " << result << std::endl; int width; TESTO_IRAPI_RESULT tresult = testo_irimage_get_width(id, &width); std::cout << "result: " << tresult << " Width: " << width << std::endl; tresult = testo_irimage_get_height(id, &width); std::cout << "result: " << tresult << " Height: " << width << std::endl; return 0; }
04-13-2018 05:25 AM
I suppose you mean that:
/*! \file \brief Native C interface */ #if defined TESTO_IRAPI_MAKEDLL //! Dll interface #define TESTO_IRAPI_EXPORT __declspec(dllexport) #else //! Dll interface #define TESTO_IRAPI_EXPORT __declspec(dllimport) #endif #ifdef __cplusplus extern "C" { ...function protoypes... }
?
This is how functions are declared in the thermal_image_api.h header file - the one that comes in Testo IR API. It looks like all Testo IR API libraries are built in C. And so I,ve included, just in any case, these in my wrapper dll source code:
#include <iostream> #include <string.h> #define TESTO_IRAPI_MAKEDLL #include "thermal_image_api.h"
04-13-2018 05:38 AM - edited 04-13-2018 05:39 AM
No I don't mean that:
declspec(dllexport) says nothing about the calling convention, only that the compiler should emit some code for function entry points and mark them for the linker to be added to the export table.
declspec(dllimport) tells the compiler that the function gets imported from a DLL, usually by the means of an import library that you link to your project.
If the function uses cdecl or stdcall calling convention is not defined by this. It is defined either by an explicit __cdecl or __stdcall keyword at the function prototype declaration and in absence of that by whatever calling convention you configured in your project settings to be used by default!
extern "C" also has nothing to do with the calling convention. It tells a compiler to omit any C++ features for this (function) declaration such as name mangling.