Features
This class adds type-safe C++ callbacks to your projects.
- Any function in any class can be called from anywhere in any other class.
- You can pass 0 to 5 arguments of any type to the callback function and specify any return type.
- A callback can be passed as an argument to any function.
- It is optimized for high speed.
- The code size is less than 1 KB, no extra libraries required.
- It is platform-independent: running on Windows, Linux, Mac, etc...
- It has been tested on Visual Studio 6.0, 7.0, 7.1 and 8.0 (= Visual Studio 6, up to .NET 2005).
- New in version 3.0 (Oct 2007): it also supports callbacks to static functions and to functions inside virtually derived classes.
Introduction
In C++, the simple address of a function is not enough to define a callback, as in old C. In C++, every instance of a class uses its own memory area for storing class variables. The this
pointer points to this area of variables. With every call to any C++ function, the this
pointer is passed invisibly to the function and additionally to the function's arguments. Microsoft Visual Studio 6 uses the processor register ECX to pass the this
pointer while the normal function arguments are pushed on the stack. To use callbacks, simply copy the files Callback.h and PreProcessor.h to your project and #include "Callback.h"
.
Why Using Callbacks?
Let's say you wrote a scheduler that reacts on certain events by executing corresponding actions. The events may be anything like arriving data, user input or timers and the actions are execution of code of any kind. The scheduler contains an endless loop in an extra thread which does nothing else other than wait for events. Under Windows, you could use the API function WaitForMultipleObjects()
, which returns the index of the event that was signaled. Such a scheduler could look like this:
The sample code accompanying this article uses callbacks for sorting a list with different callback sort functions. Note that with signals and slots, you don't have to write your own RegisterCallback()
function! See Part 2 of this article series for more details.
Defining Callbacks
There are two classes in callback.h: cCall
and cCallGen
. What happens inside these classes is complicated, so I will only explain the usage here. cCall
is defined as:
cCall <_ReturnType, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5>
You can use callback functions taking 0, 1, 2, 3, 4 or 5 arguments. If you need more than 5 arguments, you can easily expand the callback classes on your own. However, I recommend passing more than 5 arguments in a structure instead, so the code becomes more readable.
Let's say the function that you want to call back is:
int cMyApplication::Calculate(float Factor, bool Flag, char* Name)
{
......
}
For that function, you can create a corresponding callback:
cCall <int, float, bool, char*> i_CallbackCalculate;
The first template argument (int
) is always used to define the return type. The following template arguments (float, bool, char*
) are the types of the arguments passed to the callback function.
To define a callback for use with the following void returning function without arguments...
void cMyApplication::Print()
{
......
}
...write:
cCall <void> i_CallbackPrint;
Assigning Callbacks
Callbacks can be assigned to each other by using operator=
, but as these callbacks are type-safe, the following leads to a compiler error:
i_CallbackCalculate = i_CallbackPrint;
To i_CallbackCalculate
of the type int function(float, bool, char*)
, you cannot assign i_CallbackPrint
of the type void function(void)
.
Generating Callbacks
i_CallbackCalculate
and i_CallbackPrint
could not be used up to now because they were not yet initialized, which means that no function has been assigned to them. If you call i_CallbackCalculate.Valid()
, the result will be false
, which means that you cannot use the callback. If you try to (i_CallbackCalculate.Execute(...)
), the program will be aborted through an assert. To generate initialized callbacks for the above functions, you have to use cCallGen
. cCallGen
(the callback "Generator") inherits from cCall
and is only used once for initialization. cCallGen
is defined as cCallGen <_Class, _ReturnType, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5>
.
cCallGen <cMyApplication, int, float, bool, char*> i_CallGenCalculate(this, Calculate);
cCallGen <cMyApplication, void> i_CallGenPrint(this, Print);
You see that cCallGen
takes one template argument more than cCall
. The first type (cMyApplication
) is the class that contains the callback function; the rest is the same as for cCall
. The parameters passed to the constructor are the this
pointer of the class, which contains the callback function, and the callback function itself (Calculate, Print
). Now you see why there are two callback classes. cCallGen
can be used only inside the class which contains the callback function (here cMyApplication
) because it needs the this
pointer. cCall
can be created in any class and later a compatible callback passed from anywhere can be be assigned to it:
i_CallbackCalculate = i_CallGenCalculate;
i_CallbackPrint = i_CallGenPrint;
This assignment is possible because cCallGen
inherits from cCall
. Now you can execute the callback by writing:
int Result = i_CallbackCalculate.Execute(3.448, true, "Hello world");
i_CallbackPrint.Execute();
If your callback functions are static
, you have to use cCallGenS
instead of cCallGen
.
Usage Example
The following example demonstrates a list class that calls external functions to sort the data. By assigning different callback functions, you can sort the list by different criteria.
So this list becomes very flexible because the caller can manipulate the sorting behaviour without manipulating the code in cList
. This is the spirit of C++. You could do the same by inheriting from cList
and overwriting the Compare
function. However, with callbacks you can change the list behaviour dynamically at runtime by simply calling SetCallback(...)
as often as you like.
The Interface, Overview
cCall |
cCallGen (for member and virtual callbacks) |
cCallGenS (for static callbacks) |
Constructor: cCall <tRet [,tArg1,,,tArg5]> MyCall() |
Constructor: cCallGen <cClass, tRet [,tArg1,,,tArg5]> MyCallGen(Instance, Function) |
Constructor: cCallGenS <tRet [,tArg1,,,tArg5]> MyCallGen(Function) |
cCall& operator=(cCall &ExtCall) cCall& operator=(cCallGen &ExtCallGen) |
- |
- |
bool Valid() |
- |
- |
tRet Execute([tArg1, tArg2, tArg3, tArg4, tArg5]) |
- |
- |
To see how all the stuff works together, download the sample code! It is easy to understand.
Errors
You get compiler errors if you try to assign a callback to an incompatible callback (with different arguments or return type). You get runtime errors (assert) if you pass the wrong count of arguments to i_Callback.Execute(...)
or if you try to execute an uninitialized callback.
Boost
I extracted Preprocessor.h from Boost::function. Boost is a huge platform-independent C++ library (35 MB) that uses a million tricks to make the compiler do things which normally are not possible. Because every compiler behaves differently and has other restrictions, Boost contains thousands of switches, #define
s and typedef
s to make it run on all compilers. There are lots of interesting things in Boost, but also disadvantages:
- Boost is huge (35 MB).
- It slows down the compilation process.
- Sometimes it completely crashes my compiler, requiring a reboot.
- It is nearly impossible to understand the extremely cryptic code in the 100 header files used for
Boost::function
.
My callback class is much more comfortable than Boost::function
.
QT
Another library you should know about is QT. QT is a huge platform-independent GUI library. However, even if you don't want to program a GUI, QT is interesting because it offers platform-independent functions for file access, timers, etc. QT is also more: it circumvents restrictions in the compiler and even expands the C++ language itself by integrating an additional preprocessor into the compiler. This allows new functionality like easier multi-language support, runtime type information, guarded pointers, better readable code and signals and slots, which are advanced callbacks (see Part 2 of this article series). Unfortunately, QT also has disadvantages:
- It costs $1500 (except for open source Linux development).
- It is very huge (20 MB of sources).
- It slows down the compilation process.
- Programs written with QT have to deliver some additional DLLs of approximately 6 MB total size.
- Signals and slots cannot be used to return a value to the caller.
So, if you don't want the "big ones" like Boost or QT, this tiny callback class by ElmueSoft is a "nice to have" that you will not want to miss if you once got used to it.
Part 2: Type-safe C++ Signals and Slots (Events and Delegates)
Click here to get to Part 2!
Read Part 2, which is based on this article and implements a very comfortable Signal / Slot (Event / Delegate) system in C++ without requiring any big library!
- A signal (event) fired anywhere in any class of your code can be received by slots (delegates) in any class of your code.
- You can connect as many signals to a slot and as many slots to a signal as you like.
- You can pass 0 to 5 arguments of any type to a signal and you can combine the multiple return values from the slots to one single return value given back to the caller.
- If a class is destroyed, all connected signals or slots are automatically disconnected.
- Signals are reentrance-safe.
- It is optimized for high speed.
- The compiled code size is less than 1 KB, no extra libraries required.
- It is much more comfortable than
Boost::signal
s.
- Some additional extra features are available.
- It is platform-independent: running on Windows, Linux, Mac, etc...
- It has been tested on Visual Studio 6.0, 7.0, 7.1 and 8.0 (= Visual Studio 6 up to .NET 2005)
P.S. From my homepage, you can download free C++ books in compiled HTML format.
History
- 19 February, 2004 -- Original version posted
- 21 April, 2004 -- First update
- 13 February, 2008 -- Second update
- Version 3.0
- Article content and source download updated