Home > Blockchain >  Callback member function from API taking a free function with no arguments
Callback member function from API taking a free function with no arguments

Time:09-17

I use an API which the declaration is:

some_API.h

typedef void (CALLBACK   *EventCallback)();

class some_API{
public:
    // ...
    void EnableInterrupt(EventCallback func1, EventCallback func2);
    // ...
};

and on the other side I have a class that use this API:

My_class.h

class My_class
{
    some_API API;
    void CALLBACK my_func() { cout << "This is my_func!\n" }
public:
    void using_api()
    {
        API.EnableInterrupt(my_func, nullptr);
    }
};

main problem is type of my_func, error:

Error (active) E0167 argument of type "void (__stdcall My_class::*)()" is incompatible with parameter of type "EventCallback"

I found this answer, but the problem is, the API is close source, so I can't change the declaration of the void EnableInterrupt(EventCallback, EventCallback).

Also I don't want to declare My_class::using_API and My_class::API as static member.

I want something similar to this:

API.EnableInterrupt((static_cast<void*>(my_func)), nullptr);
// or
API.EnableInterrupt((static_cast<EventCallback>(my_func)), nullptr); // invalid type conversion

Is there any way to cast that member function to a non-member function to pass it to some_API::EnableInterrupt(...)?

CodePudding user response:

Your problem is that the callback type

typedef void (CALLBACK   *EventCallback)();

does not have any user-provided data pointer. This is customary for the exact reason that users often need it.

And just for completeness,

Is there any way to cast that member function to a non-member function

No, they're fundamentally different, because non-static member functions must be called with an implicit this pointer, and there's nowhere to store that in a regular function pointer. That's exactly what we'd use the user-provided pointer argument for if the API had one.

Also I don't want to declare My_class::using_API and My_class::API as static member.

How about if we generate another static for each distinct registration? We can automate it by using lambda type uniqueness:

template <typename Lambda>
class TrampolineWrapper
{
  inline static std::optional<Lambda> dest_; // optional so we can defer initialization

public:
  TrampolineWrapper(some_API *api, Lambda &&lambda)
  {
    // store your _stateful_ functor into a unique static
    dest_.emplace( std::move(lambda) );

    // register a unique _stateless_ lambda (convertible to free function)
    api->EnableInterrupt(
      [](){
        (*TrampolineWrapper<Lambda>::dest_)();
      },
      nullptr);
  }
};

// handy type-deducing function
template <typename Lambda>
TrampolineWrapper<Lambda> wrap(some_API *api, Lambda &&lambda)
{
    return {api, std::move(lambda)};
}

and use it like

class My_class
{
    some_API API;
    void my_func() { cout << "This is my_func!\n" }
public:
    void using_api()
    {
        auto wrapper = wrap(&API, [this](){ my_func(); });
       // stateful lambda          ^^^^
       // not directly convertible to a free function
    }
};

This depends on the uniqueness of lambda types to work - each lambda you use this with gets a unique static dest_ object of the same type (which may be stateful), and a unique stateless forwarding lambda (which is convertible to a free function for this horrible API).

Note that this is unique only if you have multiple distinct callers registering distinct lambdas. Two calls to My_class::using_api will still collide (the second will overwrite the first's lambda).

The only other nasty thing is that we're discarding the wrapper once the static is set up ... it'd be much nicer to keep it around so we could use it for unregistering the callback or something. You can do this if you want, but since you cannot know its type outside the scope of your lambda, it'll need to do something dynamic.

CodePudding user response:

Callbacks between C and C work well when you have something to link the callback function with your class instance. In order for this to work you need information passed from the C side (usually a void* provided on callback registration). The simplest would be to use a static method and the this pointer:

typedef void (*EventCallback)(void*)();
void EnableInterrupt(EventCallback func1, void *userdata);

static void CallbackWrapper(void* data) {
    static_cast<My_class*>(data)->my_func();
}

EnableInterrupt(CallbackWrapper, this)

Now, when your callback is something like this:

typedef void (CALLBACK   *EventCallback)();

It may be due to bad design, or it might be that the API designers didn't want you to register more than one callback handler. This may be an important issue and, considering the function will mess with interrupts, I would understand if they didn't want you to register multiple handlers.

Therefore, you need to create only one instance of your class so the singleton pattern may be a good choice. For this to work you will have to do something like this:

void CALLBACK global_callback_handler() {
  My_class::getInstance()->my_func()
}

void using_api()
{
    API.EnableInterrupt(global_callback_handler);
}
  • Related