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

make a CVICALLBACK a member function in QT creator

本文关键字:成员 函数 创建者 CVICALLBACK 成为 QT      更新时间:2023-10-16

我发现了一个关于如何使用一些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:Projectssapm3mainwindow.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]={''};
/*********************************************/
// 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 interruptn");
puts("Timestamp                 Data read   Changed Lines");
getchar();
Error:
if( DAQmxFailed(error) )
{
DAQmxGetExtendedErrorInfo(errBuff,2048);
Cleanup();
printf("DAQmx Error: %sn",errBuff);
}
printf("End of program, press Enter key to quitn");
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]={''};
char    *timeStr;
time_t  currTime;
if( taskHandle ) {
time (&currTime);
timeStr = ctime(&currTime);
timeStr[strlen(timeStr)-1]='';  // 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: %sn",errBuff);
}
return 0;
}
void Cleanup (void)
{
if( taskHandle!=0 ) 
{
/*********************************************/
// DAQmx Stop Code
/*********************************************/
DAQmxStopTask(taskHandle);
DAQmxClearTask(taskHandle);
taskHandle = 0;
}
}

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

问题是您试图传递一个成员函数指针,而不是函数指针。你可以使用间接方式来实现这一点。

在类之外,您将定义一个函数:

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) 使用比当前使用的库更高级别的库,在Qt应用程序中最好使用信号和插槽。

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

  • 建立属性赋值函数和访问器方法来设置和获取成员
  • 将回调函数声明为friend,但通常不鼓励这样做
  • 只需从回调中调用一个slot或简单方法即可为您完成任务
  • 使用函子来完成工作
  • 等等

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

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

我可能会选择成员数据访问器和赋值函数方法,或者在类中使用一个完成真正工作的专用函数,而自由回调函数只会将void*强制转换为您的类型,并在该实例上调用该方法。

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

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

NI DAQmx C++包装代码

我们最近连接了一个NI USB模拟输入卡,用于六轴力传感器。连接回叫与Vinzenz的解决方案完全相同。在我们的例子中,我们只是锁定并读取/写入一个缓冲向量来访问模拟电压值。我们的应用程序是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回调连接cvSetMouseCallback提供了C++包装cv::setMouseCallback

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

希望这些局部例子有用。