Using A C++ Object's Member Function with C-style Callbacks

C is certainly still the most commonly used language in embedded systems development. As we are migrating from C to C++, we will invariably encounter mixed codebases. In these mixed C/C++ situations, it can be challenging to understand how to fit the different development models together.

One area that can be especially confusing in mixed environments is using a C++ object's member function with a C-style callback interface. C-style callback interfaces usually involve registering a function by providing:

  1. A function pointer to the callback function
  2. A void pointer to some private data (used internally by the callback function)

C has no sense of objects, so passing in any function pointer will work just fine. However, once we need to use a C++ object's member function for a callback, things get a little more complicated. Consider a processor which has three I2C devices. Accordingly, my system has three active I2C objects. If I wanted to register a callback to a C-style API, how would I be able to indicate which object should receive the callback?

In order to get the callback data to the correct object, we need to find a way to keep track of that object's instance pointer. We can use the private data for this purpose. However, our C-style callback API doesn't touch this data, so we still won't end up in the right place.

We can create an intermediate "bounce" function whose only jobs are to cast our private data to the correct type and call the callback function.

Using C++ templates, we can do this in a generic way:

template<class T, class Method, Method m, class ...Params>
static auto bounce(void *priv, Params... params) ->
        decltype(((*reinterpret_cast<T *>(priv)).*m)(params...))
{
    return ((*reinterpret_cast<T *>(priv)).*m)(params...);
}

The bounce function interprets the private data as the instance pointer, calls the callback method, and forwards all other arguments to the callback. The callback function's return value (if any) is then passed back to the caller.

Unfortunately, declaring our templated bounce function can be pretty verbose. You need to specify the class of the object, the type of the callback method, and reference the method within the class. Certainly this is not fun to type out:

// register cb using a bounce function as the function pointer
// and our object's instance pointer as the private data
register_callback(&bounce<FooCB, decltype(&FooCB::callback),
                  &FooCB::callback>, &my_foo);

So we have created a macro to simplify using the bounce interface:

#define BOUNCE(c,m) bounce<c, decltype(&c::m), &c::m>

This gives us a much cleaner invocation:

// register cb using a bounce function as the function pointer
// and our object's instance pointer as the private data
register_callback(&BOUNCE(FooCB, callback), &my_foo);

By using this templated bounce function, we are now able to register our member function with a C-style callback interface.

Putting It All Together

You can find a simple bounce example in the embedded-resources Github repo.

Further Reading

Request for Feedback

I'm hoping to receive feedback from my more-experienced C++ brethren:

  • Is there a better way to forward a variable number of arguments to the callback function?
  • Is there a non-macro way to simplify the bounce declaration?
  • Are there more elegant ways to approach this problem?

Acknowledgements

I'd like to thank Rob C. for developing the bounce function that I described here.