Я нашел пример NI о том, как использовать некоторые функции DAQmx. Это простой 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;
}
}
Я нашел способ обойти мою проблему. Я объявляю переменную массива в верхней части файла. Таким образом, моя функция обратного вызова распознает это. Затем я копирую данные из этого массива в мой массив элементов.
Точно так же я создал переменную-счетчик и увеличиваю ее каждый раз, когда выполняется обратный вызов. В то же время я проверяю эту переменную в своей функции-члене, пока она не достигнет желаемого значения, а затем издаю сигнал. Такой подход действительно отстой, и я хочу найти более разумный способ написать это.
Проблема в том, что вы пытаетесь передать указатель на функцию-член вместо указателя на функцию. Вы можете использовать косвенное обращение, чтобы заставить это работать.
Вне класса вы определите функцию:
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 * до нужного типа.
3) Вы также можете реализовать необходимую логику внутри своего проекта, если он не слишком сложен. Таким образом, вы могли бы даже уменьшить зависимость.
Трудно сказать, какой подход подойдет вам лучше всего, так как он во многом зависит от вариантов использования и большего контекста, чем у нас здесь.
Я бы, вероятно, выбрал либо методы доступа к элементам данных и методы-мутаторы, либо выделенную функцию в вашем классе, которая выполняет реальную работу, а функция бесплатного обратного вызова просто привела бы void * к вашему типу и вызывала метод в этом экземпляре.
Это также гарантирует, что вы сможете использовать обратный вызов с кодом, не относящимся к c ++, так как вам потребуется заменить только его внутреннюю реализацию. Это также поможет позже избавиться от внешнего обратного вызова C, который вызывает метод класса.
Я думаю, что вы должны проверить следующий URL, где есть сравнительно подробный пример кода, где есть также обратный вызов для аналогичных проблем с этой библиотекой низкого уровня.
Недавно мы подключили плату аналогового ввода NI USB для использования с шестиосевым датчиком силы. Подключение обратных вызовов идентично решению Винценца. В нашем примере мы просто блокируем и читаем / записываем буферный вектор для доступа к аналоговым значениям напряжения. Наше приложение — 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;
}
}
Как указывает Винценц, это типично при подключении классов 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);
Надеюсь, что эти частичные примеры полезны.