C++ CALLBACKS (AND GIVING MEMBER FUNCTIONS TO C-STYLE CALLBACKS)

C++ Callbacks (and giving member functions to C-style callbacks)

Date Published:
Last Modified:

Overview

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:

TermDescription
CalleeA function/method/object which gets called by the caller.
CallerAn object which gets passes a callback function, and then calls (executes) it.
FunctionA basic function that does not require an instance of a class to run (e.g. standard C style functions, or static member functions).
MethodA function that belongs to an class, and requires an instance of that class to run.
SignalsTerm used for “events” in an event/listener system.
SlotsTerm 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 callback
void 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 <a href=https://replit.com/@gbmhunter/c-callback-in-cpp-using-std-function-lambda#main.cpp" target="_blank">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);
    });
    
    // 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));
}

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). Typically, the <functional> library provided by the C++ standard library will not work on embedded systems (simple test: try and include it and use std::function to see if your platform supports it). 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.


Related Content:

Tags:

comments powered by Disqus