使CVICALLBACK成为QT创建者的成员函数

时间:2013-12-30 21:18:05

标签: c++ qt qt-creator nidaqmx

我找到了一个关于如何使用某些DAQmx函数的NI示例。这是一个简单的C文件,其中包含以下内容:

...
// 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;
}

当我倾向于使用.h文件中定义的某些变量或函数时,ChangeDetectionCallback函数无法识别它们。我试图将此回调函数定义为.h文件中的成员函数,希望现在可以访问所有函数。这是我的.h内容:

#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

这是我的.c内容:

#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;
}

所以,再一次,我尝试了许多错误的方法来在头文件中定义我的回调函数失败。请帮我解决这个问题。 这是我不清楚的错误信息:

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));

这是原始代码。它触发回调函数以获取测量样本并将数据输出到控制台。我希望将采样数据写入我的成员变量,并发出一个在对象的.h文件中定义的信号。

#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;
}
}

我找到了解决问题的方法。我在文件的顶部声明了一个数组变量。这样我的回调函数就能识别它。然后,我将数据从此数组复制到我的成员数组。 同样,我创建了一个计数器变量,并在每次回调运行时递增它。同时我在我的成员函数中循环检查这个变量,直到它达到理想值,然后发出一个信号。这种方法真的很糟糕,我希望找到一种更聪明的方式来编写它。

3 个答案:

答案 0 :(得分:1)

问题在于您尝试传递成员函数指针而不是函数指针。你可以使用间接来实现这个目的。

在课堂之外,你将定义一个函数:

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

然后定义要调用的MainWindow方法:

int32 ChangeDetectionCallback(TaskHandle taskHandle, int32 signalID);

然后像这样注册:

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

请注意,callbackData参数用于将指针传递给周围的对象。注册事件时会传递此数据,而不是NULL

这是C库的典型模式,这是如何将其连接到C ++的典型方式。

答案 1 :(得分:0)

正如我几小时前在评论中提到的,您有几种方法可以解决此问题:

1)使用比您当前使用的更高级别的库,您最好使用信号和插槽,然后在Qt应用程序中使用。

2)您可以尝试将您的类实例作为原始数据传递。这将确保回调将有一个对象可以使用。当然,这需要像往常一样从void *数据重新解释为所需的类型。

  • 建立属性mutator和accessor方法来设置和获取成员
  • 将回调函数声明为朋友,但通常不鼓励这样做。
  • 只需从回调中调用一个插槽或简单方法即可为您完成工作。
  • 使用仿函数来完成工作
  • 等...

3)如果不是太复杂,你也可以自己在项目中实现必要的逻辑。这样,您甚至可以减少依赖性。

很难说哪种方法最适合您,因为它在很大程度上取决于用例和更多的上下文。

我可能会选择成员数据访问器和mutator方法,或者类中用于完成实际工作的专用函数,而自由回调函数只会将void *转换为您的类型,并调用该方法实例

这也将确保您可以稍后使用非c ++代码的回调,因为您只需要替换它的内部实现。这也有助于以后摆脱调用类方法的外部C回调。

我认为你应该查看下面的url,其中有一个相对详细的示例代码,其中还有一个针对此低级库的类似问题的回调。

NI-DAQmx C++ wrapper code

答案 2 :(得分:0)

我们最近连接了一个NI USB模拟输入卡,用于六轴力传感器。联系回拨与Vinzenz&#39;相同。解。在我们的示例中,我们只需锁定并读取/写入缓冲矢量即可访问模拟电压值。我们的应用程序是wxWidgets,但是窗口库不需要知道回调。编译器是VC10,程序是在Win7上构建的,尽管它应该可以在Linux上运行而不需要改变。 DAQmx库不断回调并填充采样电压的原始数据阵列。然后将它们平均并复制到6D向量。如果需要,可以使用自定义事件将这些事件生成回Qt或wx,例如使用下面注释掉的To_main_msg_evt。我不明白的一件事是,我们可以逃脱不包括CVICALLBACK,它仍然有效。离开它会更好吗?这似乎是一个更普遍的解决方案。我还注意到,在卡初始化后,回调不会开始大约三秒钟。

//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;
    }
}

正如Vinzenz所指出的那样,将C ++类连接到C回调是很典型的。另一个使用此方法的库是OpenCV。在这里,可以使用相同的模式设置鼠标回调,没有前导宏,并为C回调连接cv::setMouseCallback提供C ++包装器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);

希望这些部分例子很有用。