Подтвердить что ты не робот

Использование функции члена класса С++ в качестве функции обратного вызова C

У меня есть библиотека C, для которой требуется функция обратного вызова для настройки некоторой обработки. Тип функции обратного вызова int a(int *, int *).

Я пишу код на С++, похожий на следующий, и попытаюсь зарегистрировать функцию класса С++ в качестве функции обратного вызова:

class A {
  public:
   A();
   ~A();
   int e(int *k, int *j);
};

A::A()
{
   register_with_library(e)
}

int
A::e(int *k, int *e)
{
  return 0;
}

A::~A() 
{

}

Компилятор выдает следующую ошибку:

In constructor 'A::A()',
error:
 argument of type ‘int (A::)(int*, int*)’ does not match ‘int (*)(int*, int*)’.

Мои вопросы:

  • Прежде всего, возможно зарегистрировать функцию memeber класса С++, как я пытаюсь сделать, и если да, то как? (Я прочитал 32.8 на http://www.parashift.com/c++-faq-lite/mixing-c-and-cpp.html, но, на мой взгляд, это не решает проблему)
  • Есть ли альтернативный/лучший способ справиться с этим?
4b9b3361

Ответ 1

Вы можете сделать это, если функция-член статична.

Нестатические функции-члены класса A имеют неявный первый параметр типа class A*, который соответствует этому указателю. Вот почему вы могли зарегистрировать их только в том случае, если подпись обратного вызова также имела первый параметр типа class A*.

Ответ 2

Вы также можете сделать это, если функция-член не является статической, но требует немного больше работы (см. также Преобразовать указатель функции C++ в указатель функции c):

#include <stdio.h>
#include <functional>

template <typename T>
struct Callback;

template <typename Ret, typename... Params>
struct Callback<Ret(Params...)> {
   template <typename... Args> 
   static Ret callback(Args... args) {                    
      return func(args...);  
   }
   static std::function<Ret(Params...)> func; 
};

template <typename Ret, typename... Params>
std::function<Ret(Params...)> Callback<Ret(Params...)>::func;

void register_with_library(int (*func)(int *k, int *e)) {
   int x = 0, y = 1;
   int o = func(&x, &y);
   printf("Value: %i\n", o);
}

class A {
   public:
      A();
      ~A();
      int e(int *k, int *j);
};

typedef int (*callback_t)(int*,int*);

A::A() {
   Callback<int(int*,int*)>::func = std::bind(&A::e, this, std::placeholders::_1, std::placeholders::_2);
   callback_t func = static_cast<callback_t>(Callback<int(int*,int*)>::callback);      
   register_with_library(func);      
}

int A::e(int *k, int *j) {
   return *k - *j;
}

A::~A() { }

int main() {
   A a;
}

Этот пример завершен в том смысле, что он компилируется:

g++ test.cpp -std=c++11 -o test

Вам понадобится флаг c++11. В коде вы видите, что вызывается register_with_library(func), где func является статической функцией, динамически связанной с функцией-членом e.

Ответ 3

Проблема заключается в том, что метод!= function. Компилятор преобразует ваш метод в нечто подобное:

int e( A *this, int *k, int *j );

Итак, он уверен, что вы не можете передать его, потому что экземпляр класса нельзя передать как аргумент. Один из способов обойти это сделать метод статичным, таким образом он будет иметь хороший тип. Но это не будет экземпляр класса и доступ к нестационарным членам класса.

Другой способ - объявить функцию со статическим указателем на A, инициализированным в первый раз. Функция перенаправляет вызов только классу:

int callback( int *j, int *k )
{
    static A  *obj = new A();
    a->(j, k);
}

Затем вы можете зарегистрировать функцию обратного вызова.

Ответ 5

Проблема с использованием функции-члена заключается в том, что ему нужен объект, на который нужно действовать - и C не знает об объектах.

Самый простой способ - сделать следующее:

//In a header file:
extern "C" int e(int * k, int * e);

//In your implementation: 
int e(int * k, int * e) { return 0; }

Ответ 6

Это вариант шаблонного решения. Мы используем шаблонный класс со всеми членами, являющимися статическими. Когда создается экземпляр класса путем предоставления дополнения к шаблону, вы получаете одноэлементный/уникальный класс с настраиваемым методом. Это похоже на создание объекта и в то же время на определение виртуального метода.

Это реальный пример из мира Arduino. Нам нужно, чтобы задача была выполнена в отдельном потоке и была сообщена основным потоком с помощью семафора. TaskInThread скрывает все механизмы (создание семафора, создание потока и сигнализацию). Класс создается путем определения класса, который будет использоваться в качестве параметра шаблона.

// --------------------------------------------------------------
// --------------------------------------------------------------
template< typename Complement >
class TaskInThread {

  // ............................................................
  // ............................................................
  static Complement complement; // <- "virtual" (i.e. to be defined)

  // ............................................................
  // ............................................................
  static SemaphoreHandle_t mySemaphore;

  // ............................................................
  // ............................................................
  static void main( void * pData ) {
    while( true ) {
      if( xSemaphoreTake( mySemaphore, portMAX_DELAY) == pdTRUE ) {

        complement.task(); // call the actual task when we are signaled

      }
    } // while
  } // ()

public:

  // ............................................................
  // ............................................................
  static void startThread( ) {
    mySemaphore = xSemaphoreCreateBinary(); 
    xTaskCreate( main, "hello", 1024, NULL, 1, NULL ); // here, a ptr to function is needed
  } // ()

  // ............................................................
  // ............................................................
  static void signalTask() {
    xSemaphoreGive( mySemaphore );
  } // ()

}; // namespace TaskInThread

template< typename T > SemaphoreHandle_t TaskInThread<T>::mySemaphore;

// --------------------------------------------------------------
// --------------------------------------------------------------
class Task1 { // define a class with the real job to be done
public:
  static void task() {
    // do something
  }
};

// --------------------------------------------------------------
// --------------------------------------------------------------
class Task2 {
public:
  static void task() {
    // do something more
  }
};

namespace Globals {
  TaskInThread<Task1> kk; // create the customized tasks
  TaskInThread<Task2> kk2;
};

// --------------------------------------------------------------
// setup()
// --------------------------------------------------------------
void setup() {

  Globals::kk.startThread();
  Globals::kk2.startThread();

} // setup ()

// --------------------------------------------------------------
// loop ()
// --------------------------------------------------------------
namespace Loop {
  int cont = 0;
};

// ..............................................................
// ..............................................................
void loop () {

  using namespace Loop;
  using namespace Globals;

  cont++;

  kk.signalTask(); // Task1.task() is executed (by its own thread)

  delay( 1000 );

  kk2.signalTask();

  delay( 1000 );

} // loop ()

Ответ 7

В этом решении у нас есть шаблонный класс со статическим методом, который должен быть передан функции "c" в качестве обратного вызова. Этот класс содержит "обычный" объект (с функцией-членом с именем callback(), которая будет окончательно вызвана).

Как только ваш класс (здесь, A) определен, его можно легко использовать:

int main() {

  Holder<A> o ( A(23, 23) );

  std::cout << o().getN() << "\n";

  callACFunctionPtr( fun );

  callACFunctionPtr( o.callback );

} // ()

Полный пример:

#include <iostream>

// ----------------------------------------------------------
// library class: Holder
// ----------------------------------------------------------
template< typename HeldObjectType >
class Holder {
public:
  static inline HeldObjectType object;

  static void callback( ) {
    object.callback();
  } // ()

  HeldObjectType &  operator() ( ) {
    return object;
  }

  Holder( HeldObjectType && obj )
  {
    object = obj;
  }

  Holder() = delete;

}; // class

// ----------------------------------------------------------
// "old" C function receivin a ptr to function as a callback
// ----------------------------------------------------------
using Callback = void (*) (void);

// ..........................................................
// ..........................................................
void callACFunctionPtr( Callback f ) {
  f();
} // ()

// ----------------------------------------------------------
// ----------------------------------------------------------
void fun() {
  std::cout << "I'm fun\n";
} // 

// ----------------------------------------------------------
// 
// Common class where we want to write the
// callback to be called from callACFunctionPtr.
// Name this function: callback
// 
// ----------------------------------------------------------
class A {
private:
  int n;

public:

  A(  ) : n( 0 ) { }

  A( int a, int b ) : n( a+b ) { }

  void callback( ) {
    std::cout << "A callback(): " << n << "\n";
  }

  int getN() {
    return n;
  }

}; // class

// ----------------------------------------------------------
// ----------------------------------------------------------
int main() {

  Holder<A> o ( A(23, 23) );

  std::cout << o().getN() << "\n";

  callACFunctionPtr( fun );

  callACFunctionPtr( o.callback );

} // ()