06-30-2023 09:38 AM - edited 06-30-2023 09:41 AM
I have imported a DLL into LabVIEW where one of the functions requires LabVIEW to provide a call-back function. Since LabVIEW does not support function pointers, I have followed the approach described in Provide a Callback Function Pointer to My DLL Using LabVIEW - NI (and also taken the advice given in numerous posts by @rolfk) and registered my own call-back function, which in turn calls PostLVUserEvent() and attempts to pass data from the DLL to LabVIEW. However, this function always returns an error code of 1 (mgArgErr: an invalid argument was passed to the function) regardless of whether I pass a pointer to a single integer or a struct (that corresponds to a LabVIEW cluster).
I've included a screenshot of the LabVIEW VI and also the external (calling) code in case anyone can help me figure out what I'm doing wrong. (I am the author of the DLL and thus have the source code available.)
The VI sets up the User Event, which expects to receive a single integer (uInt32) when called. (In the real application, this would be a cluster; I've simplified it for testing purposes.) It then calls the external DLL function configRaiseLabviewRawDataUserEvent(), passing it an app-specific ID (uInt32) and the refnum of the user event (LVUserEventRef). Then it waits for the DLL to raise the event by calling PostLVUserEvent(). [See attachment LabVIEW_vi_with_user_event.png.]
The DLL functions are as follows. configRaiseLabviewRawDataUserEvent() stores the user event refnum and registers an internal call-back function:
extern "C" __declspec(dllexport) bool configRaiseLabviewRawDataUserEvent( hMstkHandle uiConnectionHandle, LVUserEventRef labviewRawDataUserEventId )
{
cout << "configRaiseLabviewRawDataUserEvent ..." << endl;
// Store LabVIEW User Event ID for use in the call-back function (defined below).
lvRawDataUserEventId = labviewRawDataUserEventId;
cout << "LabVIEW User Event ID: " << labviewRawDataUserEventId << endl;
// Register internal call-back function.
return configRawDataCallback( uiConnectionHandle, labviewInternalRawDataCallback );
}
The internal call-back function labviewInternalRawDataCallback() calls PostLVUserEvent() with the user event refnum and a test integer (normally this would pack and send the data received from the DLL in a structure):
void __stdcall labviewInternalRawDataCallback( hMstkHandle uiConnectionHandle, int iChannel, int iNumValues, UINT8* auiValues )
{
/* // Copy the raw data to a structure to pass to LabVIEW.
lvData.uiConnectionHandle = uiConnectionHandle;
lvData.iChannel = iChannel;
lvData.iNumValues = iNumValues;
memcpy( lvData.auiValues, auiValues, iNumValues * sizeof(UINT8) );*/
// Send the address of the structure to LabVIEW.
// MgErr mgErr = PostLVUserEvent(lvRawDataUserEventId, &lvData);
uint32_t testVar = (uint32_t)uiConnectionHandle;
MgErr mgErr = PostLVUserEvent(lvRawDataUserEventId, &testVar);
if ( mgErr != mgNoErr )
{
cout << "labviewInternalRawDataCallback: LabVIEW function PostLVUserEvent() failed with error code " << mgErr << endl;
}
}
When we run the VI, we see the following output:
...
configRaiseLabviewRawDataUserEvent ...
LabVIEW User Event ID: 675282974
configRawDataCallback ...
Set device 0 raw-data call-back function
configStartStreaming: sent {"start":"0"}
configStartStreaming: received {"result":"OK"}
configSetSpinning: sent {"spin":"on"}
configSetSpinning: received {"result":"OK"}
labviewInternalRawDataCallback: LabVIEW function PostLVUserEvent() failed with error code 1
labviewInternalRawDataCallback: LabVIEW function PostLVUserEvent() failed with error code 1
labviewInternalRawDataCallback: LabVIEW function PostLVUserEvent() failed with error code 1
labviewInternalRawDataCallback: LabVIEW function PostLVUserEvent() failed with error code 1
...
The VI never receives the event notification and the 5000ms time-out is breached.
Given that the function signature for PostLVUserEvent() is PostLVUserEvent(LVUserEventRef ref, void *data) then presumably I am calling it with the correct number of arguments. Therefore error code 1 could mean:
I am now stuck!
Solved! Go to Solution.
07-03-2023 11:37 AM
Bumping this because it was quarantined for three days and hence is no longer on the front page ...
07-03-2023 11:45 AM
iv Tried to do something similar in the past.
i think its just a limitation of the NI system that you cant do call backs in that manner.
maybe you could write an plug in C++ to handle this section?
07-03-2023 01:13 PM - edited 07-03-2023 01:15 PM
Are you calling configRaiseLabVIEWRawDataUserEvent() with the second pararameter configured to be Adapt To Type? Because if you do that LVUserEventRef will be passed by reference and the prototype should really look like this and of course you should dereference that parameter to store the actual refnum.
extern "C" __declspec(dllexport) bool configRaiseLabviewRawDataUserEvent(hMstkHandle uiConnectionHandle, LVUserEventRef *labviewRawDataUserEventId);
A recommendation if you create your own C code to interface from LabVIEW to it:
Right click on the Call Library Node and select "Create c File". The created file will contain an empty function declaration with the correct parameter datatypes. Of course if you then go and make changes in the Call Library Node, you need to do this again!
07-04-2023 03:21 AM - edited 07-04-2023 03:25 AM
I haven't looked at the C code at all, but certainly the LV code is wrong:
What you should be passing to configRaiseLabviewRawDataUserEvent is the reference of the created event. That's the blue wire coming out of the create user event primitive in the first frame. What you're actually passing is the value of the reference of the control of the registration refnum, which is wrong on several levels.
The idea is that you create an event reference, register to say "I want to get the events" (which you did) and pass the reference to the C code so it can generate the events.
I should also note that it can be dangerous to have event registration refnums coming from outside the VI, as there are some complications with how things happen if you happen to use the same registration refnum with more than one event structure. Since your example only has one event, you certainly don't need the Event Registration Refnum control and can just remove it. Unless you're changing the event registration during the run, you also don't need the shift register and can just feed the reg refnum into the event structure.
07-04-2023 04:11 AM - edited 07-04-2023 04:12 AM
You have to pass the user event refnum to your configRaiseLabviewRawDataUserEvent call. You are passing it a VI server reference to an Event Registration Refnum, which is something completely different. "Refnum" is a term that is often used without a type specifier, which can easily get confusing.
Like rolfk said, user event refnums are usually passed by reference. Be sure to dereference it in your dll, weird things happen when you store the reference and call the function another time.
As an aside, The "Event Registration Refnum" is almost never used as a control, because in most cases it is used with an immediately following event structure. There are very few exceptions to this.
-----
Edit: too slow 🙂
07-04-2023 08:27 AM
Many thanks to @rolfk, @tst and @cordm for their very helpful replies! I will make the recommended changes to the LabVIEW VI and report back ...
07-04-2023 11:39 AM
This did the trick! Thanks very much indeed to all who helped get me there.