Jump to content

LabVIEW VI and C callback


Recommended Posts

Hello,

 

I am trying to figure out, if it is possible to create a native LabVIEW I/O interface to C/C++ code, that is using the I/O functions through callbacks.

 

The scenario is as follows:

 

I made a toolkit to process multimedia formats and (so far only) video codecs, it is using internally FFmpeg shared objects (DLLs or SOs, further just "DLLs").

 

They way the FFmpeg DLLs access I/O is through callback mechanism. Following all happens in the wrapper.

If I want the toolkit to read data from some I/O like network or disk, I have to create a session, and part of the session are function pointers to callbacks doing various tasks, one of them is reading data.

 

Anytime a user tries to read more data (1 data packet), he forces a call to the session demuxer, that further uses the I/O callback so long, until it parses one packet/hits EOF/corrupt data/etc..., now this works nicely if you want to provide data through standard means like video file or stream URL.

 

Since the capabilities of FFmpeg allow for example (de)muxers and codecs for images, I thought it would be cool to give the user possibility to supply his own data, that he reads via LabVIEW from database or whatever. Not just limit the usage of I/Os implemented inside the DLLs.

 

All I know, is that I cannot use VIs as callbacks inside C/C++ code, so the only way is to somehow "decallback" the implemented I/O mechanism. 

Link to comment

You can't create C/C++ callbacks directly in LabVIEW but you can create a DLL or wrap an existing DLL that registers for the callback and generates a LabVIEW event.

How exactly do you imagine the mechanism would work?

 

The only way I can think of and I thought of before, is this (data reading example).

 

DLL code wants more data, it calls the callback that contains the PostLVUserEvent(argument contains struct pointer having data pointer and mutex pointer) and then locks itself on the mutex provided, it triggers the registered user event in already running LabVIEW VI, the VI then excutes a user provided Callback VI, after the call is done, it makes a new DLL call that takes care of 1st copying the data to the provided data pointer and 2nd unlocking the mutex the 1st function was waiting for.

 

It was very complicated :(

Link to comment

How exactly do you imagine the mechanism would work?

 

The only way I can think of and I thought of before, is this (data reading example).

 

DLL code wants more data, it calls the callback that contains the PostLVUserEvent(argument contains struct pointer having data pointer and mutex pointer) and then locks itself on the mutex provided, it triggers the registered user event in already running LabVIEW VI, the VI then excutes a user provided Callback VI, after the call is done, it makes a new DLL call that takes care of 1st copying the data to the provided data pointer and 2nd unlocking the mutex the 1st function was waiting for.

 

It was very complicated :(

 

That's more or less how you need to do it with the published LabVIEW APIs. Nothing else will really work easier or better without going the route of undocumented LabVIEW manager calls. NI does have the possibility to call VIs from within C code directly.But that is only used within LabVIEW itself, not in external code AFAIK. And that functionality may hit bad issues if used from external code.

 

Lua for LabVIEW does something similar but without using the PostLVUserEvent() as that wasn't really available when Lua for LabVIEW was developed. The Lua for LabVIEW API allows to register VIs in the C interface with a Lua function name. When the Lua bytecode interpreter encounters such a name the call is passed back to a VI deamon (a background VI process that Lua for LabVIEW starts behind the scenes) and that deamon then pulls the parameters from the Lua stack, calls the VI and pushes any return values back on the Lua stack before handing control back to the Lua engine. Quite involved and tricky to handle correctly but the only feasable way to deal with this problem.

 

There is also a lot of sanity checking of parameters and their types necessary to avoid invalid execution and crashes as you do want to avoid the situation where a user can crash the whole thing by making an error in the VI interface.

Link to comment

How exactly do you imagine the mechanism would work?

 

The only way I can think of and I thought of before, is this (data reading example).

 

DLL code wants more data, it calls the callback that contains the PostLVUserEvent(argument contains struct pointer having data pointer and mutex pointer) and then locks itself on the mutex provided, it triggers the registered user event in already running LabVIEW VI, the VI then excutes a user provided Callback VI, after the call is done, it makes a new DLL call that takes care of 1st copying the data to the provided data pointer and 2nd unlocking the mutex the 1st function was waiting for.

 

It was very complicated :(

 

Easiest way.

Create a buffer (U8 array) when you initialise the DLL event reference. (pass in an already initlaised array of bytes using the Initialise Array.vi). In the callback function; dereference the data and memcopy to the buffer you allocated at init. Then PostLVUserEvent the array. No mutexes required because memcopy is atomic and LabVIEW has its own managed memory copy. You can read the array directly out of the terminal of event structure.

 

There is no way to get out of requiring a C/C++ DLL to do this mediation for you, by the way - LabVIEW cannot create C/C++ callbacks :P If you can find a .NET version then you can have a VI callback function IF they properly advertise the event, though.

Edited by ShaunR
Link to comment

Easiest way.

Create a buffer (U8 array) when you initialise the DLL event reference. (pass in an already initlaised array of bytes using the Initialise Array.vi). In the callback function; dereference the data and memcopy to the buffer you allocated at init. Then PostLVUserEvent the array. No mutexes required because memcopy is atomic and LabVIEW has its own managed memory copy. You can read the array directly out of the terminal of event structure.

 

That's not going to work in this case like that I'm afraid without some means of synchronization. The FMpeg library does not call the callback to send data to the client but to request the next junk of data from the "stream". As such it is also not a classic callback interface but rather a stream API with a device driver handle that contains specific method function pointers for the various stream operations (open/read/write/seek/close). The Library calls thes functions and expects them to return AFTER the function has done the necessary work on the "stream".

 

I was hoping to make a LabVIEW VI, that would eat the callback prototype as parameter and build a dll out of it in LabVIEW, then just load the code in the DLL and call that in my callback. It seems much more straight forward. 

 

While not exactly impossible it is a rather complicated and cumbersome interface. You could write a VI that can act as "callback", it's not really the normal callback type but rather as explained above a driver API with a "handle" with specific access methods as function pointers in it. It's a very popular C technique for pluggable APIs, but really only easily accessible from C too. You could also see it as the C implementation of a C++ object pointer. Then compile those VIs into a DLL that you can then LoadLibrary() the entry points of it into your LabVIEW cluster mimicking that API structure with the function pointers.

 

But there are many problems with that:

 

1) The DLL will run in the LabVIEW runtime version that corresponds to the LabVIEW version used to create it. If your users are going to run this in a different LabVIEW version there will be a lot of data marshalling between the users LabVIEW system and the LabVIEW runtime in which the DLL runs.

 

2) If you want to make this pluggable with user VIs your DLL will somehow have to have a way of registering the users LabVIEW VI server instance with it so that your VI can proxy to the user VI through VI server, which adds even more marshalling overhead to the whole picture.

 

3) Every little change will require you to recreate the LabVIEW DLL, distribute it somehow and hope it doesn't break with some users setup.

 

4) The whole story about loading the DLL function pointers into a LabVIEW cluster to serve as your FMpeg library handle is utterly ugly, error prone and will in case of supporting both 32 and 64 bit LabVIEW versions also require entirely different clusters that your code needs to conditionally use depending on which bitness LabVIEW has.

 

5) The chance to get this working reliably is pretty small, will require lots and lots of work that will need to be rechecked everytime you modify something anywhere in that code and allow a user to actually mess up the whole thing if he is careless enough to go into those parts of the VIs and modify anything.

Link to comment

Easiest way.

Create a buffer (U8 array) when you initialise the DLL event reference. (pass in an already initlaised array of bytes using the Initialise Array.vi). In the callback function; dereference the data and memcopy to the buffer you allocated at init. Then PostLVUserEvent the array. No mutexes required because memcopy is atomic and LabVIEW has its own managed memory copy. You can read the array directly out of the terminal of event structure.

 

There is no way to get out of requiring a C/C++ DLL to do this mediation for you, by the way - LabVIEW cannot create C/C++ callbacks :P If you can find a .NET version then you can have a VI callback function IF they properly advertise the event, though.

The task is to get LabVIEW data to C/C++ code, not the other way around.

That's not going to work in this case like that I'm afraid without some means of synchronization. The FMpeg library does not call the callback to send data to the client but to request the next junk of data from the "stream". As such it is also not a classic callback interface but rather a stream API with a device driver handle that contains specific method function pointers for the various stream operations (open/read/write/seek/close). The Library calls this function and expects it to return AFTER it has done the necessary work on the "stream".

 

 

While not exactly impossible it is a rather complicated and cumbersome interface. You could write a VI that can act as "callback", it's not really the normal callback type but rather as explained above a driver API with a "handle" with specific access methods as function pointers in it. It's a very popular C technique for pluggable APIs, but really only easily accessible from C too. You could also see it as the C implementation of a C++ object pointer. Then compile those VIs into a DLL that you can then LoadLibrary() the entry points of it into your LabVIEW cluster mimicking that API structure with the functions pointers.

 

But there are many problems with that:

 

1) The DLL will run in the LabVIEW runtime version that corresponds to the LabVIEW version used to create it. If your users are going to run this in a different LabVIEW version there will be a lot of data marshalling between the users LabVIEW system and the LabVIEW runtime in which the DLL runs.

 

2) If you want to make this pluggable with user VIs your DLL will somehow have to have a way of registering the users LabVIEW VI server instance with it so that your VI can proxy to the user VI through VI server, which adds even more marshalling overhead to the whole picture.

 

3) Every little change will require you to recreate the LabVIEW DLL, distribute it somehow and hope it doesn't break with some users setup.

 

4) The whole story about loading the DLL function pointers into a LabVIEW cluster to serve as your FMpeg library handle is utterly ugly, error prone and will in case of supporting both 32 and 64 bit LabVIEW versions also require entirely different clusters that your code needs to conditionally use depending on which bitness LabVIEW has.

 

5) The chance to get this working reliably is pretty small, will require lots and lots of work that will need to be rechecked everytime you modify something anywhere in that code and allow a user to actually mess up the whole thing if he is careless enough to go into those parts of the VIs and modify anything.

Fair enough. Thank you for replies.

Link to comment

The task is to get LabVIEW data to C/C++ code, not the other way around.

Fair enough. Thank you for replies.

 

One possible solution might be what I did in the OpenG ZIP Library. There the open function is polymorphic and allows to select if the subsequent operations should be performed on a file on disk or on a byte array stream.

 

For the Unzip on stream operation the "stream" to read from is passed to the Open function as a LabVIEW string (really should be a byte array but traditionally LabVIEW uses strings for APIs that should work as byte stream like TCP/UDP, VISA etc.).

 

For the ZIP operation on a stream the Close function returns a LabVIEW string which contains the byte stream data. This isn't pluggable with user provided VIs that provide direct stream access as you intend and has the drawback that the entire data has to be in memory during the entire operation but it is at least possibly to reasonably implement.

Link to comment

That's not going to work in this case like that I'm afraid without some means of synchronization. The FMpeg library does not call the callback to send data to the client but to request the next junk of data from the "stream". As such it is also not a classic callback interface but rather a stream API with a device driver handle that contains specific method function pointers for the various stream operations (open/read/write/seek/close). The Library calls thes functions and expects them to return AFTER the function has done the necessary work on the "stream".

 

That sounds more like a virtual function declaration than a callback. LabVIEW supports those less than callbacks :) . A fully encapsulating C/C++ wrapper for the DLL exposing an initialiser to give a filename is probably the only option here. It sounds like the DLL isn't an API but more like just a compartmentalised piece of code that needs a lot of scaffolding to make anything useful even before you get to interfacing to  LabVIEW.

Link to comment

TIL... this is an eye-opener. Thanks!

 

(Is this documented anywhere, by the way?)

 

Not really. PosLVUserEvent() isn't really documented in any way that deserves the word documention. Yes it is mentioned in the External Code Reference manual just as all the other public LabVIEW manager functions. But for most of them that documentation goes seldom further than the function prototype, some more or less meaningful parameter names and a short description of each parameter that mostly repeats what can be derived from the parameter name too.

 

The fact that you can register callback VIs for other events than .Net events is not only not documented but has been mentioned by LabVIEW developers to be a byproduct of the event architecture of LabVIEW and shouldn't really be relied upon to always work exactly the same. Never heard anyone mentioning that they could be used for PostLVUserEvent() but it is a logical step considering that it does work for other LabVIEW user events and I was waking up this morning with the idea that this might be something to try out here. :D

 

Nice that Jack confirmed this already and the extra tidbit about being synchronous for callback VIs is an interesting information too, although logical if you think about it.

 

Of course it also allows the callback VI developer to easily block the whole system, if he ends up accessing the same API again which was invoking the callback VI!

Link to comment

Here's the game-changer -- `PostLVUserEvent()` in C will behave differently depending on how the associated event reference is registered in LabVIEW.

 

When a listener is bound to the event using `Register for Events`, `PostLVUserEvent()` is asynchronous and non-blocking, and does not wait for the consumption of the event. It "blocks" only long enough to copy data reliably into the queue shared by its message-reference-counting registrants, then moves along.

 

On the other hand, `PostLVUserEvent()` will synchronously block until the Callback VI handler has finished executing.

 

If the call to PostLVUserEvent() becomes blocking as you mentioned instead of just "broadcasting" the information to any VI that registered it, how can this handle multiple VIs registering the same Callback?

Link to comment

Skimming thru this thread... try this:

 

From C, call `PostLVUserEvent()`.

 

Then, in LabVIEW, rather than registering that event reference into a `Register for Events` node, register the event reference with a `Register Event Callback` node.

 

Here's the game-changer -- `PostLVUserEvent()` in C will behave differently depending on how the associated event reference is registered in LabVIEW.

 

When a listener is bound to the event using `Register for Events`, `PostLVUserEvent()` is asynchronous and non-blocking, and does not wait for the consumption of the event. It "blocks" only long enough to copy data reliably into the queue shared by its message-reference-counting registrants, then moves along.

 

On the other hand, `PostLVUserEvent()` will synchronously block until the Callback VI handler has finished executing.

 

This means, your library can `Post` some pointers/handles into LabVIEW, LabVIEW can fill them in the Callback VI, and downstream logic from the `PostLVUserEvent()` needs no further gymnastics to synchronize/mutex.

Thx Jack, that is new information for me. However (also based on what RolfK wrote) i will rather implement with my own mutex (or other sync object) I have more control over. Since the PostLVUserEvent has no timeout, it would lead to many unwanted locks the LabVIEW programmer would have no clue about (would happen in DLL) and no control over.

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.