How can I pass a C++ lambda to a C-callback that expects a function pointer and a context?

前端 未结 4 878
面向向阳花
面向向阳花 2020-12-05 02:43

I\'m trying to register a callback in a C-API that uses the standard function-pointer+context paradigm. Here\'s what the api looks like:

void register_callba         


        
相关标签:
4条回答
  • 2020-12-05 02:47

    A lambda function is compatible with C-callback function as long as it doesn't have capture variables.
    Force to put something new to old one with new way doesn't make sense.
    How about following old-fashioned way?

    typedef struct
    {
      int cap_num;
    } Context_t;
    
    int cap_num = 7;
    
    Context_t* param = new Context_t;
    param->cap_num = cap_num;   // pass capture variable
    register_callback([](void* context) -> void {
        Context_t* param = (Context_t*)context;
        std::cout << "cap_num=" << param->cap_num << std::endl;
    }, param);
    
    0 讨论(0)
  • 2020-12-05 02:48

    The most efficient way is to voidify the lambda directly.

    #include <iostream>
    #include <tuple>
    #include <memory>
    
    template<typename... Args, typename Lambda>
    std::pair< void(*)(void*, Args...), std::unique_ptr<void, void(*)(void*)> > voidify( Lambda&& l ) {
      typedef typename std::decay<Lambda>::type Func;
      std::unique_ptr<void, void(*)(void*)> data(
        new Func(std::forward<Lambda>(l)),
        +[](void* ptr){ delete (Func*)ptr; }
      );
      return {
        +[](void* v, Args... args)->void {
          Func* f = static_cast< Func* >(v);
          (*f)(std::forward<Args>(args)...);
        },
        std::move(data)
      };
    }
    
    void register_callback( void(*function)(void*), void * p ) {
      function(p); // to test
    }
    void test() {
      int x = 0;
      auto closure = [&]()->void { ++x; };
      auto voidified = voidify(closure);
      register_callback( voidified.first, voidified.second.get() );
      register_callback( voidified.first, voidified.second.get() );
      std::cout << x << "\n";
    }
    int main() {
      test();
    }
    

    here voidify takes a lambda and (optionally) a list of arguments, and generates a traditional C-style callback-void* pair. The void* is owned by a unique_ptr with a special deleter so its resources are properly cleaned up.

    The advantage of this over a std::function solution is efficiency -- I eliminated one level of run-time indirection. The lifetime that the callback is valid is also clear, in that it is in the std::unique_ptr<void, void(*)(void*)> returned by voidify.

    unique_ptr<T,D>s can be moved into shared_ptr<T> if you want a more complex lifetime.


    The above mixes lifetime with data, and type erasure with utility. We can split it:

    template<typename... Args, typename Lambda>
    std::pair< void(*)(void*, Args...), std::decay_t<Lambda> > voidify( Lambda&& l ) {
      typedef typename std::decay<Lambda>::type Func;
      return {
        +[](void* v, Args... args)->void {
          Func* f = static_cast< Func* >(v);
          (*f)(std::forward<Args>(args)...);
        },
        std::forward<Lambda>(l)
      };
    }
    

    Now voidify does not allocate. Simply store your voidify for the lifetime of the callback, passing a pointer-to-second as your void* along side the first function pointer.

    If you need to store this construct off the stack, converting the lambda to a std::function may help. Or use the first variant above.

    0 讨论(0)
  • 2020-12-05 02:57

    The simple aporoach is to stick the lambda into a std::function<void()> which is kept somewhere. Potentially it is allocated on the heap and merely referenced by the void* registered with the entity taking the callback. The callback would then simply be a function like this:

    extern "C" void invoke_function(void* ptr) {
        (*static_cast<std::function<void()>*>(ptr))();
    }
    

    Note that std::function<S> can hold function objects with state, e.g., lambda functions with a non-empty capture. You could register a callback like this:

    register_callback(&invoke_function,
      new std::function<void()>([=](){ ... }));
    
    0 讨论(0)
  • 2020-12-05 03:06

    A very simple way to get use a lamda function as a C funtion pointer is this:

    auto fun=+[](){ //add code here }
    

    see this answer for a example. https://stackoverflow.com/a/56145419/513481

    0 讨论(0)
提交回复
热议问题