3

I am using a C library in a C++ application. The C SDK has functions that take a callback function pointer as an argument. The signature of these functions is usually like:

typedef int (* Func) (type1 c, type2 d);

I have my code structured using classes in C++. However, I can't pass any member functions to this function as callback because it doesn't accept int (MyClass::*)(type1 c, type2 d) and only accepts int (*)(type1 c, type2 d).

I'm getting around this by defining all my callbacks as static in the various classes and then passing them to the C library which then works.

I am still new to C++ so I'm not sure if this is the right solution? The code works, but I'd love to hear if I'm doing this wrong.

Plasty Grove
  • 2,807
  • 5
  • 31
  • 42

3 Answers3

6

On most platforms your code is ever likely to encounter, your solution is fine. However, strictly speaking, there can be a platform where the C calling convention and C++ calling convention differ. On this platform, your code would not work.

The 100% correct solution (as opposed to the 99.9% correct one which you use) is to define a function with C language linkage as the callback. If you need this function to have member access to your class, it can then call a static member function in your class:

class MyClass
{
  static int secret;
public:
  static void callback() { secret = 42; }
};

extern "C" void callback() { MyClass::callback(); }

Note that it is considered good practice for authors of callback registration points (that is, authors of the library which will call you back) to provide a way for the user to associated void* data with the callback, which will be passed in as they call you. If your library allows that, you can use this void *user_data to pass in a pointer to your C++ class. With such a nicely-designed callback library, it could look like this:

class MyClass
{
  int secret;

public:
  void setSecret() { secret = 42; }
};

extern "C" void callback(void *userData) { static_cast<MyClass*>(userData)->setSecret(); }

MyClass mc;

int main()
{
  cLibrary_registerCallback(&callback, &mc);
}
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Thank you! This took a fair bit of meditation before I understood what is going on. The SDK does have void* for some of the functions, but I haven't been using them in the right way. I'm going to see if I can switch away from statics to member functions. – Plasty Grove Feb 14 '19 at 15:10
  • A follow up question, if I wanted to pass an argument to the callback, how would I do that. For example, in the second example, what if I wanted to pass a `string` to `setSecret` i.e. `static_cast(userData)->setSecret("Hello");`? – Plasty Grove Feb 15 '19 at 15:21
  • @PlastyGrove `setSecret` in the second example is fully under your control and you can do whatever you want with it. The signature of `callback` is dictated by the library (as that is the one who calls it). If you need to store some data "alongside" the callback (so that the callback receives it when called), you have to make it part of `userData`. For example, have `userData` point not to `MyClass`, but to a `std::pair` or something similar (and manage its lifetime correctly, of course). – Angew is no longer proud of SO Feb 15 '19 at 19:52
  • I see, let me give that a shot. I guess I'll have to create a few member level `std::pair` variables and assign `this` before passing it to the callback register function. Thanks! – Plasty Grove Feb 16 '19 at 00:43
3

The member functions always implicitly get 'this' as their first argument. The use of static functions often works as static functions don't need this implicit argument. Please see Angew's answer with extern "C" thing to get 100% portability.

Pavel Shishpor
  • 742
  • 6
  • 14
  • This is true as far as it goes, but it doesn't address language linkage. C++ functions are not required to use the same calling conventions as C functions, so, as other answers have pointed out, for maximum portability, callbacks should be non-member functions marked as `extern "C"`. – Pete Becker Feb 14 '19 at 14:09
  • @PeteBecker yes, you are right. I edited the answer to reflect that. – Pavel Shishpor Feb 14 '19 at 14:57
1

To call a non-static member function as a call back, you need to define a non-member function, which takes an instance as an argument, and calls the member function on that instance. Such C API's that use a function pointer callback also allow registering a void pointer to user defined data that will be forwarded to the call back. Use this pointer to pass the instance.

Instead of a named function, it is typical to use a lambda, because such simple wrapper is typically not reusable and has no need for a name.

eerorika
  • 232,697
  • 12
  • 197
  • 326