make a CVICALLBACK a member function in QT creator

1.2k views Asked by At

I found an NI example on how to use some of the DAQmx functions. It's a simple C-file that contains some of the following:

...
// This is a declaration/definition I think
int32 CVICALLBACK ChangeDetectionCallback(TaskHandle taskHandle, int32 signalID, void *callbackData);
...
// Later in the script there is actual function

int32 CVICALLBACK ChangeDetectionCallback(TaskHandle taskHandle, int32 signalID, void *callbackData)
{
    ...
return 0;
}

When I tend to use some of the variables or functions that are defined in .h file, the ChangeDetectionCallback function does not recognize them. I tried to define this callback function as a member function in .h file, hoping that now all functions will be accessible. Here's my .h content:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "NIDAQmx.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();

int32 CVICALLBACK ChangeDetectionCallback(TaskHandle taskHandle, int32 signalID, void *callbackData);

private:
Ui::MainWindow *ui;
void mainLoop();
};

#endif // MAINWINDOW_H

and here's my .c content:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "NIDAQmx.h"

#include <stdio.h>
#include <string.h>
#include <time.h>

#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
mainLoop();
}

MainWindow::~MainWindow()
{
delete ui;
}

void MainWindow::mainLoop()
{
...
DAQmxErrChk (DAQmxRegisterSignalEvent(taskHandle,DAQmx_Val_ChangeDetectionEvent,0,ChangeDetectionCallback,NULL));
...    
}




int32 MainWindow::ChangeDetectionCallback(TaskHandle taskHandle, int32 signalID, void *callbackData)
{
...
return 0;
}

So, again, I tried many wrong ways to define my callback function in the header file unsuccessfully. Please, help me to get this straight. And here's the error message that I do not clearly understand:

D:\Projects\sapm3\mainwindow.cpp:37: error: cannot convert 'MainWindow::ChangeDetectionCallback' from type 'int32 (MainWindow::)(TaskHandle, int32, void*) {aka long int (MainWindow::)(void*, long int, void*)}' to type 'DAQmxSignalEventCallbackPtr {aka long int (__attribute__((__cdecl__)) *)(void*, long int, void*)}'
     DAQmxErrChk (DAQmxRegisterSignalEvent(taskHandle,DAQmx_Val_ChangeDetectionEvent,0,ChangeDetectionCallback,NULL));

Here's the original code. It triggers callback function to get a measurement sample and outputs the data to console. I wish to write the sampled data to my member variable and emit a signal that is defined in the .h file of the object.

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <NIDAQmx.h>

#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else

static TaskHandle   taskHandle;
static uInt32       numLines;
static uInt8        cachedData[200];

int32 CVICALLBACK ChangeDetectionCallback(TaskHandle taskHandle, int32 signalID, void *callbackData);
void Cleanup (void);

int main(void)
{
int32       error=0;
char        errBuff[2048]={'\0'};

/*********************************************/
// DAQmx Configure Code
/*********************************************/
DAQmxErrChk (DAQmxCreateTask("",&taskHandle));
DAQmxErrChk (DAQmxCreateDIChan(taskHandle,"Dev1/port0/line0:7","",DAQmx_Val_ChanPerLine));
DAQmxErrChk (DAQmxCfgChangeDetectionTiming(taskHandle,"Dev1/port0/line0:7","Dev1/port0/line0:7",DAQmx_Val_ContSamps,1));
DAQmxErrChk (DAQmxRegisterSignalEvent(taskHandle,DAQmx_Val_ChangeDetectionEvent,0,ChangeDetectionCallback,NULL));
DAQmxErrChk (DAQmxGetTaskNumChans(taskHandle,&numLines));

/*********************************************/
// DAQmx Start Code
/*********************************************/
DAQmxErrChk (DAQmxStartTask(taskHandle));

puts("Continuously reading. Press Enter key to interrupt\n");

puts("Timestamp                 Data read   Changed Lines");

getchar();

Error:
if( DAQmxFailed(error) )
{
    DAQmxGetExtendedErrorInfo(errBuff,2048);
    Cleanup();
    printf("DAQmx Error: %s\n",errBuff);
}
printf("End of program, press Enter key to quit\n");
getchar();
return 0;
}

int32 CVICALLBACK ChangeDetectionCallback(TaskHandle taskHandle, int32 signalID, void *callbackData)
{
int32   error=0;
uInt8   data[200]={0};
int32   numRead;
uInt32  i=0;
char    buff[512], *buffPtr;
char    errBuff[2048]={'\0'};
char    *timeStr;
time_t  currTime;

if( taskHandle ) {
    time (&currTime);
    timeStr = ctime(&currTime);
    timeStr[strlen(timeStr)-1]='\0';  // Remove trailing newline.

    /*********************************************/
    // DAQmx Read Code
    /*********************************************/
    DAQmxErrChk (DAQmxReadDigitalLines(taskHandle,1,10.0,DAQmx_Val_GroupByScanNumber,data,8,&numRead,NULL,NULL));

    if( numRead ) {
        buffPtr = buff;
        strcpy(buff, timeStr);

        strcat(buff,"  ");
        buffPtr = buff + strlen(buff);
        for(;i<numLines;++i) {
            sprintf(buffPtr,"%d",data[i]);
            buffPtr++;
        }

        strcat(buff,"    ");
        buffPtr = buff + strlen(buff);
        for(i=0;i<numLines;++i) {
            sprintf(buffPtr,"%c",data[i]==cachedData[i]?'-':'X');
            buffPtr++;
            cachedData[i] = data[i];
        }
        puts(buff);
        fflush(stdout);
    }
}
return 0;

Error:
if( DAQmxFailed(error) )
{
    DAQmxGetExtendedErrorInfo(errBuff,2048);
    Cleanup();
    printf("DAQmx Error: %s\n",errBuff);
}
return 0;
}

void Cleanup (void)
{
if( taskHandle!=0 ) 
{
    /*********************************************/
    // DAQmx Stop Code
    /*********************************************/
    DAQmxStopTask(taskHandle);
    DAQmxClearTask(taskHandle);
    taskHandle = 0;
}
}

I found the way to go around my problem. I declare an array variable at the top of the file. This way my callback function recognizes it. Then, I copy data from this array to my member array. Similarly, I created a counter variable and increment it each time the callback runs. At the same time I loopcheck this variable in my member function until it reaches desirable value and then emit a signal. Such approach really sucks and I wish to find a more intelligent way to writ it.

3

There are 3 answers

1
Vinzenz On BEST ANSWER

The problem is that you're trying to pass a member function pointer instead of a function pointer. You could use an indirection to get this working.

Outside of the class you'll define a function:

int32 CVICALLBACK ChangeDetectionCallbackWrapper(TaskHandle taskHandle, int32 signalID, void *callbackData) {
    MainWindow * this_ = reinterpret_cast<MainWindow*>(callbackData);
    return this_->ChangeDetectionCallback(taskHandle, signalID);
}

Then define the MainWindow method to be called like this:

int32 ChangeDetectionCallback(TaskHandle taskHandle, int32 signalID);

And then register it like this:

DAQmxRegisterSignalEvent(taskHandle,DAQmx_Val_ChangeDetectionEvent,0,ChangeDetectionCallbackWrapper,this));

Note that the callbackData parameter is used for passing the pointer to the object around. This data is passed when you register the event, instead of the NULL.

This is a typical pattern for C libraries, and this is a typical way of how to connect this to C++.

0
Patrick K On

We have recently hooked up a NI USB analog input card for use with a six-axis force sensor. Hooking up the call backs is identical to Vinzenz' solution. In our example we just lock and read/write a buffer vector to access the analog voltage values. Our application is wxWidgets but the windowing library does not need to be aware of the callbacks. The compiler is VC10 and the program is built on Win7 although it should work on Linux without change. The DAQmx library keeps calling back and filling a raw data array of sampled voltages. These are then averaged and copied to a 6D vector. One can generate these events using custom events back to Qt or wx if needed such as with the commented out To_main_msg_evt below. One thing I don't understand is that we can get away with not including the CVICALLBACK and it still works. Is it better to leave it? It seems like a more general solution without. I have also noticed that the callbacks don't start coming for about three seconds after the card is initialized.

//in ATI_force.hpp there is

int32 static Every_n_callback(  TaskHandle task_handle, 
                                int32 every_n_samples_evt_type,
                                uInt32 n_samples, 
                                void* obj_ref);

int32 Every_n_callback( TaskHandle task_handle, 
                        int32 every_n_samples_evt_type, 
                        uInt32 n_samples);


//in ATI_force.cpp in the Init() function there is

for(int i = 0; i < 6; ++i)
{
    channel_port = ports_names[i];
    channel_name = channels_names[i];

    if(still_ok)
    {
        still_ok = NI_ok(DAQmxCreateAIVoltageChan(  _task_handle, 
                                                    channel_port.c_str(), 
                                                    channel_name.c_str(),
                                                    DAQmx_Val_Cfg_Default,
                                                    -10.0, //min max volts params
                                                    10.0,
                                                    DAQmx_Val_Volts,
                                                    NULL));
    }
}
if(still_ok) 
{
    //todo what is the 1000 param ->
    //indicate continuous sampling at so many milliseconds (3rd param) 
    still_ok = NI_ok(DAQmxCfgSampClkTiming(_task_handle, "", _sample_every_ms, DAQmx_Val_Rising, DAQmx_Val_ContSamps, 1000));   
}
if(still_ok)
{
    //register the read callback Every_n_callbaint 
    int callback_every_n_samples(10); //<-effets speed of aquisition
    still_ok = NI_ok(DAQmxRegisterEveryNSamplesEvent(_task_handle, DAQmx_Val_Acquired_Into_Buffer, callback_every_n_samples, 0, Every_n_callback, this));               
}

//other useful functions

//used for the interface to the class
bool ATI_force::Read_all_channels(arma::vec6& vals_out)
{
    bool success(false);
    if(_is_initialized)
    {
        _chan_vals_mutex.lock();
        vals_out = _chan_vals;
        _chan_vals_mutex.unlock();
        success = true;
    }
    return success;
}

//the callback and its static wrapper

int32 ATI_force::Every_n_callback(TaskHandle task_handle, int32 every_n_samples_evt_type, uInt32 n_samples, void* obj_ref)
{
    ATI_force* obj_ptr(reinterpret_cast<ATI_force*>(obj_ref)); //obj_ref = "this"
    return obj_ptr->Every_n_callback(task_handle, every_n_samples_evt_type, n_samples);
}

int32 ATI_force::Every_n_callback(TaskHandle task_handle, int32 every_n_samples_evt_type, uInt32 n_samples)
{
    int32 ret(-1);
    bool still_ok(true);
    //{
    //  std::ostringstream oss;
    //  oss << "In Every_n_callback: " << std::endl;
    //  To_main_msg_evt ev(oss.str(), true);
    //  wxPostEvent(_parent, ev);
    //}
    //lock the mutex on the data and write to it 
    _num_read = 0;
    if(_is_initialized)
    {
        still_ok = NI_ok(DAQmxReadAnalogF64(_task_handle, 
                                            _num_samples, 
                                            _read_timeout_ms,  
                                            DAQmx_Val_GroupByChannel, 
                                            _data_buff.memptr(),  //writes over old vals
                                            _data_buff.size(), 
                                            &_num_read, 
                                            NULL)); //this or NULL in last param?? todo
        _chan_vals_buffer.zeros(); //zero out the values either way
        if(still_ok)
        {
            //for all six channels
            for(int j = 0; j < 6; ++j)
            {
                //average the samples
                for(int i = j*_num_samples; i < (j + 1)*_num_samples; ++i)
                {
                    _chan_vals_buffer.at(j) += _data_buff.at(i);
                }
                _chan_vals_buffer.at(j) /= static_cast<double>(_num_samples);
            }

        }
    }
    else
    {
        still_ok = false;
    }
    if(still_ok)
    {

        Condition_vals_out(_chan_vals_buffer);

        _chan_vals_mutex.lock();
        _chan_vals = _chan_vals_buffer; //this is the handoff to _chan_vals
        _chan_vals_mutex.unlock();
    }
    if(still_ok)
    {
        ret = 0;
    }
    return ret;
}

//the usage in the main form is roughly

void M_Frame::On_ati_test_btn_click(wxCommandEvent& event)
{
    if(!_ATI_force)
    {
        _ATI_force.reset(new ATI_force(this, 400.0, 50.0));
        boost::posix_time::seconds wait_time(5);
        boost::this_thread::sleep(wait_time);
    }

    double val_out(0.0);

    arma::vec6 vals_out;
    vals_out.zeros();

    if(_ATI_force->Is_initialized())
    {
        //_ATI_force->Reset_bias();
        std::cout << "_ATI_force Is Initialized." << std::endl;

        int num_reads(5);
        Stopwatch sw;
        sw.Restart();
        double total_time(0.0);
        double avg_time(0.0);

        _ATI_force->Bias_vals_out(vals_out);
        for(int i = 1; i < num_reads; ++i)
        {
            if(_ATI_force->Read_all_channels(vals_out))
            {
                std::cout << "voltages =" << vals_out << std::endl;
            }
            else
            {
                std::cout << "Read failed." << std::endl;
            }
        }
        total_time = static_cast<double>(sw.Get_elapsed_us());
        avg_time = total_time/static_cast<double>(std::max(num_reads - 1, 1));
        std::cout << "average read time = " << avg_time <<  "us" << std::endl;
    }
}

As Vinzenz points out this is typical when connecting C++ classes to C callbacks. Another library that uses this method is OpenCV. Here it is possible to set up mouse callbacks using the same pattern with no leading macros and a supplied C++ wrapper cv::setMouseCallback for the C callback hookup cvSetMouseCallback.

//in .hpp

static void Mouse_handler(int event, int x, int y, int flags, void* param); //param = this
void Mouse_handler(int event, int x, int y, int flags); 

//in Template_select()

//...
cv::namedWindow(_template_select, CV_WINDOW_AUTOSIZE);
cv::imshow(_template_select, _temp_select);
cv::waitKey(30);
cv::setMouseCallback(_template_select, Mouse_handler, this);

//...
cv::destroyWindow(_template_select);

Hope these partial examples are useful.

5
László Papp On

As I noted in the comments a couple of hours ago, you have several ways to resolve this issue:

1) Use a higher-level library than the one you are currently using where you could preferably use signals and slots then as ideal in a Qt application.

2) You could try passing your class instance as raw the data. This would ensure that the callback will have an object to work with. Of course, that would need reinterpret_cast'ing from the void* data to the desired type as usual.

  • Establish property mutator and accessor methods to set and get the members
  • Declare the callback function as friend, but that is usually discouraged.
  • Just call a slot or simple method from the callback to do the job for you.
  • Use a functor for doing the job
  • etc...

3) You could also implement the necessary logic yourself inside your project if is not too complex. That way, you could even reduce the dependency.

It is difficult to say which approach would suit you the best as it depends a lot on the use cases and more context than we have here.

I would probably go for either member data accessor and mutator methods, or a dedicated function in your class which does the real job, and the free callback function would just cast the void* to your type, and call the method on that instance.

This would also ensure that you can use the callback with non-c++ code later as you would need to replace the internal implementation of it, only. This would also help later with getting rid of the external C callback which calls the class method.

I think you should check out the following url where there is a relatively detailed sample code, where there is also a callback for similar issues with this low-level library.

NI-DAQmx C++ wrapper code