10-18-2024 01:35 AM
I have a problem in writing a wrapper for Port audio dll in LabVIEW. The sequence is query the sound devices and find the one which supports ASIO and has outputs and then start a stream using PortAudio DLL. The function prototypes in the header file are as follows:
typedef void PaStream;
int Pa_Initialize( void );
int Pa_OpenStream( PaStream** stream);
int Pa_StartStream( PaStream *stream );
int Pa_StopStream( PaStream *stream );
There is a C example using the following sequence:
err = Pa_Initialize();
err = Pa_OpenStream(&stream)
err = Pa_StartStream( stream );
err = Pa_StopStream( stream );
Now to use this in LabVIEW, I read the following threads:
https://forums.ni.com/t5/LabVIEW/Creating-a-pointer-to-void-to-pointer/td-p/3582162
https://forums.ni.com/t5/LabVIEW/Passing-a-Void-pointer-to-a-DLL-in-Labview/td-p/2756716
and based on that I created the attached example_sine.vi, which uses LabVIEW:DSNewPtr to generate a pointer and pass its value and pointer to the functions, but it is giving the error: Invalid stream pointer. Any help will be highly appreciated.
Solved! Go to Solution.
10-18-2024 02:23 AM
You do not need to create a pointer. In fact, you cannot allocate a PaStream since the type is intentionally hidden from you. (portaudio.h:697: typedef void PaStream;)
DSNewPtr allocates memory and returns a pointer to it. You tried to allocate zero bytes which probably fails so that you get back a null pointer. The library checks for null pointers which leads to the error message.
Instead, just pass a pointer sized integer by pointer to Pa_OpenStream. Pass the returned value to every function (by value) that expects a PaStream*.
10-18-2024 03:45 AM
Note that on 32 bit LabVIEW, you shouldn't use pointer sized integers.
Sadly, pointer sized integers take over your OS bit size. Modern Windows is 64 bit only, so pointer sized integers will be 64 bits:
Specifies the exact type of the data selected in the Type control.
On a 32 bit process, pointers are 32 bit, not 64 bit, though...
Pointer sized integer on 64 bit LV:
Pointer sized integer on 32 bit LV:
10-18-2024 05:45 AM
@Mahyara a écrit :
I created the attached example_sine.vi, which uses LabVIEW:DSNewPtr to generate a pointer and pass its value and pointer to the functions, but it is giving the error: Invalid stream pointer. Any help will be highly appreciated.
You don't need to deal with DSNewPtr at all. Your stream is just 64 bit address (allocated by library), so you need to pass just U64 "holder":
That is.
You have couple of other troubles (with callback where address passed instead of value).
Minimal but functional example (very quick and dirty) example, based on c code, which plays sine wave on default audio device:
in the attachment downgraded to LV2018.
10-18-2024 06:27 AM
@Andrey @Cordm @Wiebe
Many many thanks for your helps! I could run Andrey example to find my mistakes.
10-18-2024 08:24 AM
@Mahyara wrote:
@Andrey @Cordm @Wiebe
Many many thanks for your helps! I could run Andrey example to find my mistakes.
It will run faster if you run it in any thread.
It will read better if you turn on names of those CLFNs.
10-18-2024 09:07 AM - edited 10-18-2024 09:10 AM
wiebe@CARYA a écrit :
@Mahyara wrote:
@Andrey @Cordm @Wiebe
Many many thanks for your helps! I could run Andrey example to find my mistakes.It will run faster if you run it in any thread.
It will read better if you turn on names of those CLFNs.
No, if you already entered DLL, then single call will not run faster in "Any Thread". Thread is thread. The only penalties which are occurred — between the calls, because before calling DLL in UI thread the LabVIEW will wait for free UI thread (which is occupied time to time for UI updates, user interactions, property nodes, etc). Most critical part here is the for-loop, which continuously writes data to the output stream. But in general, you're right (and in this particular case the whole UI will be blocked until sound playing and the sound could be interrupted during UI activities), but usually if I have "foreign" DLL in my hands, then I start development with UI thread, because this will eliminate all possible issues in case if the given DLL is not thread-safe, and only once everything works fine, only then I switching to thread-safe, because in this case each call will be performed in own thread (and other than UI thread).
But it seems to be fine also thread-safe as well, it works:
The given example is just starting point, later each CLFN should be placed into SubVI with meaningful icons (that was already partially done in initial post, but with some bugs).
10-18-2024 10:49 AM - edited 10-18-2024 10:54 AM
@Andrey_Dmitriev wrote:
wiebe@CARYA a écrit :
@Mahyara wrote:
@Andrey @Cordm @Wiebe
Many many thanks for your helps! I could run Andrey example to find my mistakes.It will run faster if you run it in any thread.
It will read better if you turn on names of those CLFNs.
No, if you already entered DLL, then single call will not run faster in "Any Thread". Thread is thread. The only penalties which are occurred — between the calls, because before calling DLL in UI thread the LabVIEW will wait for free UI thread (which is occupied time to time for UI updates, user interactions, property nodes, etc)
Exactly. So once the UI thread gets busy with other (LV) stuff, you'll pay the price.
You won't have that problem if it runs in any thread.
@Andrey_Dmitriev wrote:
But in general, you're right (and in this particular case the whole UI will be blocked until sound playing and the sound could be interrupted during UI activities), but usually if I have "foreign" DLL in my hands, then I start development with UI thread, because this will eliminate all possible issues in case if the given DLL is not thread-safe, and only once everything works fine, only then I switching to thread-safe, because in this case each call will be performed in own thread (and other than UI thread).But it seems to be fine also thread-safe as well, it works:
Thread safety is not an issue at all if you synchronize the calls.
Thread safety is only a problem if a) the dll isn't thread safe and b) you call the dll in parallel threads. So, yes, running in the UI thread solves this. But making sequential dll calls hides the problem altogether.
10-18-2024 01:07 PM - edited 10-18-2024 01:40 PM
wiebe@CARYA wrote:
Note that on 32 bit LabVIEW, you shouldn't use pointer sized integers.
Unfortunately, that is wrong advice if you work with pointers to DLL functions. If it is a pointer you ALWAYS should configure the Call Library Node to use pointer sized integers for such a parameter. Yes, in LabVIEW it will be always treated as a 64-bit (unsigned) integer but that is intentional and correct, since LabVIEW is in its datatypes always bit size strict. The Call Library Node will, for pointer sized parameters, correctly convert from the 64-bit value to the 32-bit pointer when it executes in 32-bit LabVIEW and leave it at 64-bit when running in 64-bit LabVIEW. It also will correctly sign-extend any returned 32-bit pointer, when running under 32-bit, to the according 64-bit value.
Exactly. So once the UI thread gets busy with other (LV) stuff, you'll pay the price.
Not only the LabVIEW UI drawing! Any other Call Library Node set to run in the UI thread will delay your code too. There is also "root loop" functionality such as popup menu tracking, that will actually suspend anything else that should happen in the UI thread. All in all it can really add up and your audio streaming might be blocked by the user selecting a right click popup menu and not being able to decide which of the many beautiful options he should select.
Thread safety is only a problem if a) the dll isn't thread safe and b) you call the dll in parallel threads. So, yes, running in the UI thread solves this. But making sequential dll calls hides the problem altogether.
Sequentializing DLL calls to make sure that there never can be more than one function from the DLL called, is a possibility to call thread unsafe DLLs without having to resort to the UI Thread setting. But that requires of course a very strict discipline from the programmer using your library. If you know exactly what you are doing and don't give your library to normal LabVIEW users for use, that is an option. Requiring a typical LabVIEW user to observe such rules most likely will give you a blank stare and when you thought you explained it well enough, they will sooner or later still make a mistake.
But there are other potential thread problems. For instance if the DLL uses Thread Local Storage (TLS). It means that it reserves from the OS a TLS slot to store a pointer in there and if you use the reentrant setting in the Call Library Node, LabVIEW can and actually often will execute the DLL function from varying threads. But that means that each function invocation gets its own TLS value (it is tied to the actual thread) and the whole idea of sharing a value between function invocations fails. Why use TLS? Well, if you store it in a global, any invocation will use the same value so multiple independent calls will interfere with each other, if you store it in TLS, you can have multiple calling threads each working with their own "private global". This works quite well in environments where the caller explicitly starts up worker threads to do something. Each worker thread then can call the library functions at will and still work properly without interfering with other threads calling the same functions. In LabVIEW, with its implicit thread multithreading this works not well. Only code set to run in the UI thread is always guaranteed to execute in that same thread. Of course use of TLS is kind of nasty in nowadays environments where threads are not as clear as they used to be in good old C programming. The absolutely proper way is to have a function use an actual parameter in which it returns a handle, context or similar that the caller than has to hold onto, to later call the same or other functions to work on that information in that handle.
10-18-2024 02:36 PM
@rolfk a écrit :
wiebe@CARYA wrote:
Note that on 32 bit LabVIEW, you shouldn't use pointer sized integers.
Unfortunately, that is wrong advice if you work with pointers to DLL functions. If it is a pointer you ALWAYS should configure the Call Library Node to use pointer sized integers for such a parameter. Yes, in LabVIEW it will be always treated as a 64-bit (unsigned) integer but that is intentional and correct
I fully agree with the part that for pointers, the pointer-sized integer needs to be configured (personally, I always use unsigned, because for me pointers are unsigned). It is very convenient if you have the same VIs for both 32 and 64-bit environments. However, I absolutely disagree that in a 32-bit environment they are treated as 64-bit. This probably happened because some software or systems engineers were lazy enough to design a more elegant solution other than simple interpretation of 32-bit pointers as 64-bit. As a result, SubVIs above using stream as input will have 64-bit on the connector pane also when opened in 32-bit LabVIEW, leaving the LSB part unused. I will keep my personal opinion regardless of NI's interpretation and your explanation (which I could understand as well), based on a major principle of democracy — freedom of speech. My statement is: In a 32-bit environment, the pointers are 32 bits. I really don't want to see 64-bit here; any operation with 64-bit in a 32-bit environment is overhead, usually involving two registers — there are no 64-bit registers available at all in 32 bit env (I almost not using the 32-bit any longer, but the overall principle is imporant!). Why are we not saving 8 or 16-bit gray images always in 64 bits? That could also simplify our developers' work significantly, isn't?
@rolfk a écrit :Sequentializing DLL calls to make sure that there never can be more than one function from the DLL called, is a possibility to call thread unsafe DLLs without having to resort to the UI Thread setting.
In most cases yes, but not always. The problem is that even if the calls are done sequentially, it is still not guaranteed that they will be executed (called) from the same thread, and some DLLs could be 'sensitive' to context switches. One example is the old NVIDIA CUDA library. To get this beast running, a 'special' NI Compute wrapper library was introduced to force all calls of all functions within the same thread, but not the UI thread (it is possible, but some work is required). In my career, I've encountered such a third-party DLL only once, and I finally used NI Compute for 32-bit and later my own solution for 64-bit.By the way, it was hard to explain my problem to the developer because he accepted the fact that the library was not thread-safe (parallel calls were not required), but he was unable to understand the crazy development environment where each call could be made from a different thread. (I'm too lazy to prepare a demo that would show how sequential calls could lead to a crash).