Search code examples
c++pointerscastingmember-functions

Callback member function from API taking a free function with no arguments


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(...)?


Solution

  • 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.