C++ Callbacks (and giving member functions to C-style callbacks)
Callbacks are functions which are passed to other functions (or modules, libraries e.t.c) that then call the function at their choosing.
C++, being a strongly-typed object-orientated language, makes callbacks a tricker subject to deal with than say, in C (non-object orientated) or Javascript (object orientated but NOT strongly typed). This is especially true for embedded systems where you cannot always rely on having newer C++ standard library header files such as <functional>
at your disposal (it’s a mixed bag, some embedded C++ environments I’ve used do support <functional>
, others do not).
Terminology
First, let’s get some terminology out of the way:
Term | Description |
---|---|
Callee | A function/method/object which gets called by the caller. |
Caller | An object which gets passes a callback function, and then calls (executes) it. |
Function | A basic function that does not require an instance of a class to run (e.g. standard C style functions, or static member functions). |
Method | A function that belongs to an class, and requires an instance of that class to run. |
Signals | Term used for “events” in an event/listener system. |
Slots | Term used for objects which listen to signals in an event/listener system. These are normally implemented with a callback system. |
The Primitive C++ Callback: The Caller Knows The Type Of The Callee
The problem arises when you want to pass in a non-static method (function belonging to an class, that requires an instance of that class) as a callback to a library. A method is a member function of an object. To call a method, you can’t just know the functions memory address and call it, you also have to know the object that the function belongs to (the this
pointer!).
This means that for C++ method callbacks in their most primitive form, the callee has to know the type of the object the function belongs to. For example:
(run this code at https://replit.com/@gbmhunter/pure-cpp-method-callback#main.cpp):
#include <cstdio>
class MyClass {public: // This is our application callback handler for when a message is received, we will // pass this into the library which deals with message parsing void onMsg(int num1, int num2) { printf("onMsg() called with num1=%i, num2=%i\n", num1, num2); }};
class LibraryClass {public: // For the library class to call the onMsg, it has to be passed both an instance // of MyClass and a pointer to the member function to call // Note that MyClass has to be known here! This creates undesired coupling...in // reality your library should never have to know about MyClass void passACallbackToMe(MyClass* myClass, void (MyClass::* onMsg)(int num1, int num2)) { // Call the callback function (myClass->*onMsg)(1, 2); }};
int main() { MyClass myClass; LibraryClass libraryClass;
// Provide the instance and function to call libraryClass.passACallbackToMe(&myClass, &MyClass::onMsg);}
Using Static Methods Or Non-Member Functions (C-Style)
If you are stuck with a C-style callback, there is no direct way to call non-static (i.e. takes a this
pointer as the magic first parameter) member function. You can however easily call static member functions (they are no different in type signature to C-style functions):
(run this code at https://replit.com/@gbmhunter/c-callback-in-cpp-using-static-method#main.cpp):
#include <cstdio>
class MyClass {public: // This is our application callback handler for when a message is received, we will // pass this into the library which deals with message parsing // The "static" keyword makes it easy, as now this function does not // take a this pointer and has the same signature as a plain C function static void onMsg(int num1, int num2) { printf("onMsg() called with num1=%i, num2=%i\n", num1, num2); // NOTE: Can't call any non-static method functions here! }};
class LibraryClass {public: // For the library class to call the onMsg, it has to be passed both an instance // of MyClass and a pointer to the member function to call // Note that MyClass has to be known here! This creates undesired coupling...in // reality your library should never have to know about MyClass void passACallbackToMe(void (*onMsg)(int num1, int num2)) { // Call the callback function onMsg(1, 2); }};
int main() { MyClass myClass; LibraryClass libraryClass;
// Provide the instance and function to call libraryClass.passACallbackToMe(&myClass.onMsg);}
Note the main limitation of the above method is that no non-static member functions can be called. This limits how “object orientated” you can get with your software/firmware architecture.
Using Static Methods Or Non-Member Functions, But Calling Member Functions Through Shared Variables
As we touched on before, if you are stuck with a C-style callback, there is no direct way to call a member function. However, given we can call static methods (as shown directly above), we can use shared variables (e.g. file scoped variables) to call a particular instance from that static function. This is about the best you can do when you can’t change the type signature of the C-style callback.
Let’s go through an example, this time using a standard C function rather than a static member function purely for illustration they are interchangeable:
(run this code at https://replit.com/@gbmhunter/c-callback-in-c-using-global-vars-and-funcs):
#include <cstdio>#include <functional>
class LibraryClass {public: void passACallbackToMe(int (*callback)(int num1, int num2)) { // Now invoke (call) the callback int o = callback(1, 2); printf("Value: %i\n", o); // We might be on an embedded system, use printf() and not std::cout }};
class MyClass {public: int methodToCallback(int num1, int num2) { return num1 + num2; }};
// Global pointer to an instance of our class so the C style callback// wrapper can invoke the callback on a particular instance (yuck!)MyClass * myClassPtr;int cStyleWrapper(int num1, int num2) { return myClassPtr->methodToCallback(num1, num2);}
int main(){ MyClass myClass; // Make the global variable point to our new instance. Obviously, this // way does not scale well, as you have to make global variable and C-style // function for every instance (and what if you don't know how many instances you will // need!?!) myClassPtr = &myClass;
LibraryClass libraryClass; libraryClass.passACallbackToMe(&cStyleWrapper);}
Static Variables, With Templating
A slightly more complicated but flexible approach to the above is to use templating, std::bind
and std::function
as shown in the below example:
(run this code at https://replit.com/@gbmhunter/c-callback-in-cpp-using-templating-functional-bind#main.cpp):
#include <stdio.h>#include <functional>
template <typename T>struct Callback;
template <typename Ret, typename... Params>struct Callback<Ret(Params...)> { template <typename... Args> static Ret callback(Args... args) { return func(args...); } static std::function<Ret(Params...)> func;};
template <typename Ret, typename... Params>std::function<Ret(Params...)> Callback<Ret(Params...)>::func;
// C-style API which just wants a standard function for callbackvoid c_function_which_wants_callback(int (*func)(int num1, int num2)) { int o = func(1, 2); printf("Value: %i\n", o);}
class ClassWithCallback { public: int method_to_callback(int num1, int num2) { return num1 + num2; }};
typedef int (*callback_t)(int,int);
int main() { ClassWithCallback my_class; Callback<int(int,int)>::func = std::bind(&ClassWithCallback::method_to_callback, &my_class, std::placeholders::_1, std::placeholders::_2); callback_t func = static_cast<callback_t>(Callback<int(int,int)>::callback);
// Now we can pass this function to a C API which just wants a standard function callback c_function_which_wants_callback(func);}
Using std::function
If you have authorship of the library wanting to callback, then you can do even better than described above. What I would recommend here is to change the signature away from a C-style callback and use std::function
and lambdas instead. Rather than the library accepting a C-style callback in the format int (callback*) (int num1, int num2)
, update the library to accept a std::function<int(int, int)> callback
. This allows you to pass in a lambda, which as you’ll see below, allows you to easily call a member function.
(run this code at https://replit.com/@gbmhunter/c-callback-in-cpp-using-std-function-lambda#main.cpp):
#include <cstdio>#include <functional>
class LibraryClass {public: void passACallbackToMe(std::function<int(int, int)> callback) { // Now invoke (call) the callback int o = callback(1, 2); printf("Value: %i\n", o); // We might be on an embedded system, use printf() and not std::cout }};
class MyClass {public: int methodToCallback(int num1, int num2) { return num1 + num2; }};
int main(){ MyClass myClass;
LibraryClass libraryClass; // Use a lambda to capture myClass and call the member method libraryClass.passACallbackToMe([&myClass](int num1, int num2) -> int { return myClass.methodToCallback(num1, num2); });}
Rather than a lambda like in the example above, you can use std::bind
instead. I strongly recommend the lambda way of doing things, but for sake of completeness let’s introduce the std::bind
technique
(run this code at https://replit.com/@gbmhunter/c-callback-in-cpp-using-std-function-bind#main.cpp):
#include <cstdio>#include <functional>
class LibraryClass {public: void passACallbackToMe(std::function<int(int, int)> callback) { // Now invoke (call) the callback int o = callback(1, 2); printf("Value: %i\n", o); // We might be on an embedded system, use printf() and not std::cout }};
class MyClass {public: int methodToCallback(int num1, int num2) { return num1 + num2; }};
int main(){ MyClass myClass;
LibraryClass libraryClass;
// Alternate way to using a lambda, use std::bind instead. However I recommend the lambda way. libraryClass.passACallbackToMe(std::bind(&MyClass::methodToCallback, myClass, std::placeholders::_1, std::placeholders::_2));}
C++ Callback Libraries
cpgf
Website: http://www.cpgf.org/
- Uses the signals and slots syntax
- Callbacks can be functions, member methods, virtual methods…
- Really easy to use syntax.
- Powerful.
License: Apache License, Version 2.0
libsigc++
Website: http://libsigc.sourceforge.net/
- Supports signals and slots.
- Many features.
- Powerful.
- Uses advanced C++ compiler features.
- Somewhat complex to use
License: GNU Library General Public License
Vlpp
Vlpp is an open source C++ library which provides cross-platform replacements for <functional>
(among other std libraries). Sometimes, the <functional>
library provided by the C++ standard library will not work on embedded systems (I’ve had it work fine on some embedded platforms and not on others). Vlpp can be used as a substitute, allowing you to use vl::Func<void(void)>
to replace std::function<void(void)>
and implement callbacks in this manner on embedded platforms.
External Resources
Callbacks In C++ Using Template Functors is a great page explaining and analysing all the different ways for implementing callbacks in C++. This includes the functional model, single rooted hierarchy, parameterize the caller, callee mix-in, and functors (which they promote).
The Type-safe Callbacks In C++ library on the Code Project gives a great, complete callback library for C++ which allows callbacks with 0 to 5 input arguments.
Functors to Encapsulate C and C++ Function Pointers is a short and simple tutorial on using functors.