1

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

Useless
  • 64,155
  • 6
  • 88
  • 132
  • 3
    Non-static member functions are not the same as non-member functions! A non-static member function *must* have an object to be called on, which a `static` member function or a non-member function doesn't need. That makes such pointer incompatible. – Some programmer dude Sep 06 '21 at 12:55
  • 1
    With the above said, the normal way to go around that limitation, is to either make sure that the API can handle any kind of function (for example using [`std::function`](https://en.cppreference.com/w/cpp/utility/functional/function)); Or if the API is based on C have a generic pointer which can be passed as an argument to the call-back, and which can be used to pass a pointer to the object. – Some programmer dude Sep 06 '21 at 12:58
  • So is there any to get pointer of that non-static member function withing the object? something like: `void (*pfunc)() = obj->my_func()` , and then pass it to `API.EnableInterrupt()` – Alireza Nikpay Sep 06 '21 at 13:00
  • 1
    Doesn't the API provide a version of the callback that allows for a user defined parameter (aka: a void*). If it does not, you will have to make your class a singleton because you cannot recover the `this` pointer from a static function. – ichramm Sep 06 '21 at 13:02
  • That's the only version of the callback and as I said, I can't change the declaration, so I can use `std::function` Also I don't to change the design to singleton, but maybe I should! – Alireza Nikpay Sep 06 '21 at 13:04
  • 1
    Figured out a way to do this, but it's a real bodge to get around the sad fact that this is a badly-designed API – Useless Sep 06 '21 at 14:36
  • Does this answer your question? [c++: Difference between member and non member functions](https://stackoverflow.com/questions/5920916/c-difference-between-member-and-non-member-functions) – Ulrich Eckhardt Sep 06 '21 at 17:00

2 Answers2

2

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.

Useless
  • 64,155
  • 6
  • 88
  • 132
  • Thanks, it's worked. I just keep reading this on and on after you post it, but till now I don't know what are you doing in that code, can you please explain it? – Alireza Nikpay Sep 07 '21 at 13:41
  • Do you recognize the [lambda](https://en.cppreference.com/w/cpp/language/lambda) syntax? Do you know how they work? If you tell me where you get lost, I'll have some idea what to explain ... – Useless Sep 07 '21 at 15:33
  • I read these "[Introduction to lambdas]" (https://www.learncpp.com/cpp-tutorial/introduction-to-lambdas-anonymous-functions/) and "[Lambda captures]" (https://www.learncpp.com/cpp-tutorial/lambda-captures/). I think you are passing the lambda to the constructor and call enable of that specific object and call the lambda there! But I don't understand why `my_func()` is not type of `My_class::*` anymore, or what is this line do: ```(*TrampolineWrapper::dest_)();``` – Alireza Nikpay Sep 08 '21 at 09:46
  • 1
    `myFunc` is unchanged. I am indeed passing a lambda to the constructor via `wrap`, because it wouldn't be enough to store a `void (My_class::*)()` pointer (set to `myFunc`) in the trampoline: you can't call a non-static member function without an object to call it on. You need to store a `My_class*` as well, and at that point its simpler to wrap it into a lambda. – Useless Sep 08 '21 at 11:46
  • 1
    A trampoline, btw, is an established name for a function that just redirects to another function. Here I'm redirecting from a free function (with no arguments or implicit `this` instance pointer) to your lambda, which itself calls `my_func` on the `this` pointer it captured – Useless Sep 08 '21 at 11:49
  • 1
    So the call chain from an interrupt is `interrupt -> anonymous stateless lambda defined in the trampoline constructor -> lambda defined in using_api -> my_func` (although the middle two or last three steps can probably be inlined together). – Useless Sep 08 '21 at 12:41
  • That's much better. Thanks a lot. – Alireza Nikpay Sep 08 '21 at 13:38
1

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);
}
Useless
  • 64,155
  • 6
  • 88
  • 132
ichramm
  • 6,437
  • 19
  • 30