Search code examples
c++qtqt-creatornidaqmx

make a CVICALLBACK a member function in QT creator


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.


Solution

  • 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++.