06-09-2022 06:08 AM
Hi! I'm trying to make a C++ class that handles a DAQmx data aquisition task. I am using DAQmxRegisterEveryNSamplesEvent to call a callback when I collected enough samples to process. I wanted to add the callback as a member function of my class, but doing so causes errors:
E0167 argument of type "int32 (__cdecl NiDAQmxHandler::*)(TaskHandle handle, int32 everyNsamplesEventType, uInt32 nSamples, void *callbackData)" is incompatible with parameter of type "DAQmxEveryNSamplesEventCallbackPtr"
C3867 'NiDAQmxHandler::readData': non-standard syntax; use '&' to create a pointer to member
Removing the function from the class and simply keeping it in the cpp file works fine.
What is the syntax for adding a callback as a member function of a class?
Here's a slimmed down example:
main.cpp
#include "nidaqmxhandler.h"
int main(int argc, char *argv[])
{
NiDAQmxHandler taskhandler;
taskhandler.initDataCollection();
taskhandler.startTask();
return 0;
}
nidaqmxhandler.h
#pragma once
#include <NIDAQmx.h>
class NiDAQmxHandler
{
public:
NiDAQmxHandler();
~NiDAQmxHandler() = default;
void initDataCollection(void);
void startTask();
private:
int32 CVICALLBACK readData(TaskHandle taskHandle, int32 everyNsamplesEventType, uInt32 nSamples, void* callbackData);
TaskHandle taskHandle;
};
nidaqmxhandler.cpp
#include "nidaqmxhandler.h"
#include <vector>
#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else
int32 CVICALLBACK NiDAQmxHandler::readData(TaskHandle handle, int32 everyNsamplesEventType, uInt32 nSamples, void* callbackData)
{
printf("Test successful");
return 0;
}
void NiDAQmxHandler::initDataCollection(void)
{
DAQmxCreateTask("", &taskHandle);
DAQmxCreateAIVoltageChan(taskHandle, "Dev3/ai0", "", DAQmx_Val_Cfg_Default, -10.0, 10.0, DAQmx_Val_Volts, NULL);
DAQmxCfgSampClkTiming(taskHandle, "", 1000.0, DAQmx_Val_Rising, DAQmx_Val_ContSamps, 100);
DAQmxRegisterEveryNSamplesEvent(taskHandle, DAQmx_Val_Acquired_Into_Buffer, 100, 0, readData, NULL);
return;
}
void NiDAQmxHandler::startTask()
{
DAQmxStartTask(taskHandle);
return;
}
Thanks!
07-07-2022 10:32 AM - edited 07-07-2022 10:33 AM
It needs to be a static function. Non static class methods all have an implicit “this” pointer as first parameter, through which the class instance parameter is passed in.
07-08-2022 01:33 AM - edited 07-08-2022 01:34 AM
Hi!
That works in the above example, but making the function static means that it cannot be tied to a specific instance, and therefore cannot access the instance's members - which is usually what you need and why you are making a class in the first place.
I have found that I can give "this" as the callbackData parameter if I keep the callback outside the class (feels wildly unsafe and unintuitive), but that also means that I cannot pass any other data to the callback, which kinda cripples the callback functionality...
For instance, say that the readData function above also modifies a member, like a simple int counter for how many times you read data, how would you go about doing that?
07-08-2022 01:50 AM - edited 07-08-2022 02:07 AM
@guybrush_threepwood wrote:
Hi!
That works in the above example, but making the function static means that it cannot be tied to a specific instance, and therefore cannot access the instance's members - which is usually what you need and why you are making a class in the first place.
I have found that I can give "this" as the callbackData parameter if I keep the callback outside the class (feels wildly unsafe and unintuitive), but that also means that I cannot pass any other data to the callback, which kinda cripples the callback functionality...
For instance, say that the readData function above also modifies a member, like a simple int counter for how many times you read data, how would you go about doing that?
Well, yes the callback data pointer is how such parameters need to be passed. And they can't use an implicit class instance parameter for this as those callback APIs need to be available for standard C users too (the entire DAQmx API is standard C after all and while you can call standard C APIs in C++, the opposite is not possible, unless you want to resort to assembly and are willing to tie your implementation to one specific C++ compiler as C++ interfaces are generally NOT ABI compatible between compilers and sometimes even versions of the same compiler).
But there is nobody who said that you can only pass either the class instance pointer OR something else as callback data pointer. The solution is to define your own structure that contains ALL those elements you want to have access to in your callback, allocate a memory block on the heap for this, initialize its content and pass the pointer as callback data. Of course you now created a resource management problem, so you either need to store that pointer somewhere where you can clean it up or if the callback function has a reasonable moment where you know that this is going to be the last function, you can clean up inside the callback.
As to having functions outside the class, well you have a problem anyhow already even if the method would be a member of the class. Callbacks are inherently asynchronous (it's the main purpose of callbacks) and the actual "this" instance pointer can have been deleted at the time of invocation of your callback function independent if that function is a member of the class definition or not. As such it is unsafe no matter what and it is your responsibility to make sure that you uninstall the callback BEFORE you destroy the class instance, which would be a pretty good thing to do at latest early on in your class destructor, unless your class has other explicit methods that should disable the callback. I still would add a check in the destructor to make sure it is definitely disabled, just in case that the user of your class decides to delete the object without properly going through the entire deinitialization sequence.
And concurrent access to data elements is a standard problem in any multithreading programming and also comes into play with callbacks. The solutions is to either use atomic accessors or to use mutexes to protect access to these elements. Most modern C compilers have nowadays intrinsics that let you access data elements in an atomic manner. OS APIs also support atomic operators such as the entire slew of Interlocked Variable Access APIs. The Linux kernel has the same, as does have MacOSX, of course each with their own unique names.