LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Problem with a DLL Pointer and Pointer to Pointer in LabVIEW

Solved!
Go to solution

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.

0 Kudos
Message 1 of 21
(590 Views)

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*.

cordm_0-1729236025074.png

 

 

Message 2 of 21
(574 Views)

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:

 

  • Data type

    Specifies the exact type of the data selected in the Type control.

    Note: If you use the Signed Pointer-sized Integer or Unsigned Pointer-sized Integer numeric data types, the Call Library Function Node adapts to the specific operating system it is being executed on and returns data of the appropriate size to the library function. On 64-bit platforms LabVIEW translates these numeric data types to 64-bit integer types. On 32-bit platforms LabVIEW translates the numeric data types to 32-bit integer types.

 

On a 32 bit process, pointers are 32 bit, not 64 bit, though...

 

Pointer sized integer on 64 bit LV:

wiebeCARYA_0-1729241034407.png

 

Pointer sized integer on 32 bit LV:

wiebeCARYA_1-1729241118776.png

 

 

Message 3 of 21
(550 Views)
Solution
Accepted by topic author Mahyara

@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":

 

Screenshot 2024-10-18 12.38.29.png

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:

snippet.png

in the attachment downgraded to LV2018.

Message 4 of 21
(533 Views)

@Andrey @Cordm  @Wiebe
Many many thanks for your helps! I could run Andrey example to find my mistakes.

Message 5 of 21
(509 Views)

@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.

0 Kudos
Message 6 of 21
(483 Views)

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:

thread-safe.png

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).

0 Kudos
Message 7 of 21
(473 Views)

@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.

0 Kudos
Message 8 of 21
(455 Views)

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.

 

Rolf Kalbermatter
My Blog
Message 9 of 21
(394 Views)

@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).

0 Kudos
Message 10 of 21
(369 Views)