动态创建函数并获取指针

时间:2017-05-18 15:08:46

标签: c++ function pointers arduino interrupt

我正在使用Arduino和电机编码器来跟踪电机的旋转。为此,我在Arduino上使用中断。我可以创建一个函数ISR,只要信号在引脚上发生变化,就会由处理器执行。中断/ ISR组合的工作方式如下:

void setup() {
    attachInterrupt(1,ISR_function,FALLING);
}

void ISR_function() {
    // do something
}

看到我有多台带编码器的电机,我决定上课来处理这个问题。但是,attachInterrupt方法需要一个函数指针,我知道在C ++中你不能有一个指向对象实例的方法函数的指针。所以这样的事情不会起作用:

class Encoder {
    public:
        Encoder(void);
        void ISR_function(void);
    private:
        // Various private members
}

Encoder::Encoder() {
    attachInterrupt(1,ISR_function,FALLING);
}

Encoder::ISR_function() {
    // Do some interrupt things with private members
}

因为ISR_function不是静态的。然而,ISR_function执行的代码依赖于每个特定实例的私有数据成员。

是否可以动态创建函数?然后检索指向该函数的指针?几乎像在javascript中一样:

class Encoder {
    public:
        Encoder(void);
        void* ISR_function(void);
    private:
        // Various private members
}

Encoder::Encoder() {
    attachInterrupt(1,ISR_function(),FALLING);
}

Encoder::ISR_function() {
    return dynamicFunctionPointer;
}

这可能吗?如果没有,如何在不手动创建单独的静态ISR_functions的情况下完成我想要做的事情。

3 个答案:

答案 0 :(得分:3)

// type of an interrupt service routine pointer
using ISR = void(*)();

// a fake version of the environment we are working with
// for testing purposes
namespace fake_environment {
    enum bob{FALLING};

    ISR isrs[100] = {0};

    void attachInterrupt(int i, void(*f)(), bob) {
        isrs[i] = f;
    }

    void runInterrupt(int i) {
        isrs[i]();
    }
}

// type storing a pointer to member function
// as a compile-time constant
template<class T, void(T::*m)()>
struct pmf {};

// stores a pointer to a class instance
// and a member function.  Invokes it
// when called with operator().  Type erases
// stuff down to void pointers.
struct funcoid {
  using pfunc = void(*)(void*);
  pfunc pf = 0;
  void* pv = 0;
  void operator()()const { pf(pv); }
  template<class T, void(T::*m)()>
  funcoid(T* t, pmf<T,m>):
    pv(t)
  {
    // create a lambda, then decay it into a function pointer
    // this stateless lambda takes a void* which it casts to a T*
    // then invokes the member function m on it.
    pf = +[](void* pt) {
      (static_cast<T*>(pt)->*m)();
    };
  }
  funcoid()=default;
};

// a global array of interrupts, which have a this pointer
// and a member function pointer type erased:
namespace client {
  enum {interrupt_count = 20};
  std::array<funcoid, interrupt_count> interrupt_table = {{}};
  // with a bit of work, could replace this with a std::vector        
}

// some metaprogramming utility code
// this lets me iterate over a set of size_t at compile time
// without writing extra helper functions at point of use.
namespace utility {
  template<std::size_t...Is>
  auto index_over( std::index_sequence<Is...> ) {
    return [](auto&& f)->decltype(auto) {
      return f(std::integral_constant<std::size_t, Is>{}...);
    };
  }
  template<std::size_t N>
  auto index_upto( std::integral_constant<std::size_t, N> ={} ) {
    return index_over( std::make_index_sequence<N>{} );
  }
}

// builds an array of interrupt service routines
// that invoke the same-index interrupt_table above.
namespace client {
  // in g++, you'd write a helper function taking an `index_sequence`
  // and take the code out of that lambda and build the array there:
  std::array<ISR, interrupt_count> make_isrs() {
    // creates an array of ISRs that invoke the corresponding element in interrupt_table.
    // have to do it at compile time, because we are generating 20 different functions
    // each one "knows" its index, then storing pointers to them.
    // Could be done with a lot of copy-pasta or a macro
    return ::utility::index_upto< interrupt_count >()(
      [](auto...Is)->std::array<ISR, interrupt_count>{
        return {{ []{ interrupt_table[decltype(Is)::value](); }... }};
      }
    );
  }
  // isr is a table of `void(*)()`, suitable for use
  // by your interrupt API.  Each function pointer "knows" its
  // index, which it uses to invoke the appropraite `interrupt_table`
  // above.
  auto isr = make_isrs();
  // with a bit of work, could replace this with a std::vector        
}

// interrupt is the interrupt number
// index is the index in our private table (0 to 19 inclusive)
// t is the object we want to use
// mf is the member function we call
// kind is FALLING or RISING or the like
// index must be unique, that is your job.
template<class T, void(T::*m)()>
void add_interrupt( int interrupt, int index, T* t, pmf<T, m> mf, fake_environment::bob kind ) {
  client::interrupt_table[index] = {t, mf};
  fake_environment::attachInterrupt(interrupt,client::isr[index],kind);
}


class Encoder {
  public:
    Encoder():Encoder(1, 7) {};
    Encoder(int interrupt, int index);
    void ISR_function(void);
    // my choice for some state:
    std::string my_name;
};

Encoder::Encoder(int interrupt, int index) {
  add_interrupt( interrupt, index, this, pmf<Encoder, &Encoder::ISR_function>{}, fake_environment::FALLING );
}

void Encoder::ISR_function() {
  // display state:
  std::cout << my_name << "\n";
}

int main() {
  Encoder e0;
  e0.my_name = "Hello World";
  fake_environment::runInterrupt(1);
  Encoder e1(0, 10);
  e1.my_name = "Goodbye World";
  fake_environment::runInterrupt(0);
}

不能用g ++编译并使用C ++ 14.

解决您的问题。 g ++问题在make_isrs中,可以用详细的复制粘贴初始化来代替。 C ++ 14来自index_uptoindex_over,可以类似地为C ++ 11重新编写。

Live example

然而,ISR应该是最小的;我怀疑你应该只记录消息并在其他地方处理它而不是与对象状态交互。

答案 1 :(得分:0)

要调用成员函数,您需要一个实例来调用它,因此它似乎不适合用于中断。

来自pointers-to-members

如果没有要在其上调用它的对象,则成员函数是没有意义的。

非静态成员函数具有与this指针对应的隐藏参数。 this指针指向对象的实例数据。系统中的中断硬件/固件无法提供此指针参数。您必须使用“普通”函数(非类成员)或静态成员函数作为中断服务例程。

一种可能的解决方案是使用静态成员作为中断服务例程,并使该函数在某处找到应该在中断时调用的实例/成员对。因此,效果是在中断上调用成员函数,但由于技术原因,您需要首先调用中间函数。

答案 2 :(得分:0)

首先,您可以提取指向类方法的指针并调用它:

auto my_method_ptr = &MyClass::my_method;
....
(myClassInstance->*my_method_ptr)(); // calling via class ptr
(myclassInstance.*my_method_ptr)(); // calling via class ref

这基本上将myClassInstance指针作为隐式参数传递给MyClass::my_method,可通过this访问。

不幸的是,AVR中断控制器不能调用类方法,因为硬件只对简单指针进行操作,并且不能用隐式参数调用该方法。你需要一个包装函数。

MotorEncoderClass g_motor; // g_ for global

void my_isr() {
    g_motor.do_something();
}

int main() {
    // init g_motor with relevant data
    // install my_isr handler
    // enable interrupts
    // ... do rest of stuff
    return 0;
}
  1. 将您的类实例创建为全局变量。
  2. 创建调用该方法的普通函数
  3. 使用相关数据初始化您的运动类
  4. 安装my_isr作为IRQ处理程序。
  5. 按开始开始:)