The simple answer to this first question is that a callback function is a function that is called through a function pointer.
If you pass the pointer (address) of a function as an argument to another, when that pointer is used to call the function
it points to it is said that a call back is made.
Why Should You Use Callback Functions?
Because they uncouple the caller from the callee. The caller doesn't care who the callee is; all it knows is that there
is a callee with a certain prototype and probably some restriction (for instance, the returned value can be int,
but certain values have certain meanings).
If you are wondering how is that useful in practice, imagine that you want to write a library that provides implementation
for sorting algorithms (yes, that is pretty classic), such as bubble sort, shell short, shake sort, quick sort, and others.
The catch is that you don't want to embed the sorting logic (which of two elements goes first in an array) into your functions,
making your library more general to use. You want the client to be responsible to that kind of logic. Or, you want it to be used
for various data types (ints, floats, strings, and so on). So, how do you do it? You use function pointers and make callbacks.
A callback can be used for notifications. For instance, you need to set a timer in your application.
Each time the timer expires, your application must be notified. But, the implementer of the time'rs mechanism doesn't know
anything about your application. It only wants a pointer to a function with a given prototype, and in using that pointer it
makes a callback, notifying your application about the event that has occurred. Indeed, the SetTimer() WinAPI uses a callback
function to notify that the timer has expired (and, in case there is no callback function provided, it posts a message to the
application's queue).
Another example from WinAPI functions that use callback mechanism is EnumWindow(), which enumerates all the top-level windows
on the screen. EnumWindow() iterates over the top-level windows, calling an application-provided function for each window,
passing the handler of the window. If the callee returns a value, the iteration continues; otherwise, it stops.
EnumWindows() just doesn't care where the callee is and what it does with the handler it passes over.
It is only interested in the return value, because based on that it continues its execution or not.
However, callback functions are inherited from C. Thus, in C++, they should be only used for interfacing C code and existing
callback interfaces. Except for these situations, you should use virtual methods or functors, not callback functions.
A Simple Implementation Example
typedef int (__stdcall *CompareFunction)(const byte*, const byte*);
void DLLDIR __stdcall Bubblesort(byte* array,
int size,
int elem_size,
CompareFunction cmpFunc);
void DLLDIR __stdcall Quicksort(byte* array,
int size,
int elem_size,
CompareFunction cmpFunc);
These two functions take the following parameters:
- byte* array: a pointer to an array of elements (doesn't matter of which type)
- int size: the number of elements in the array
- int elem_size: the size, in bytes, of an element of the array
- CompareFunction cmpFunc: a pointer to a callback function with the prototype listed above
- -1: if the first element is lesser and/or should go before the second element (in a sorted array)
- 0: if the two elements are equal and/or their relative position doesn't matter (each one can go before the other in a sorted array)
- 1: if the first element is greater and/or should go after the second element (in a sorted array)
void DLLDIR __stdcall Bubblesort(byte* array,
int size,
int elem_size,
CompareFunction cmpFunc)
{
for(int i=0; i < size; i++)
{
for(int j=0; j < size-1; j++)
{
// make the callback to the comparison function
if(1 == (*cmpFunc)(array+j*elem_size,
array+(j+1)*elem_size))
{
// the two compared elements must be interchanged
byte* temp = new byte[elem_size];
memcpy(temp, array+j*elem_size, elem_size);
memcpy(array+j*elem_size,
array+(j+1)*elem_size,
elem_size);
memcpy(array+(j+1)*elem_size, temp, elem_size);
delete [] temp;
}
}
}
}
Note: Because the implementation uses memcpy(), these library functions should not be used for types other than POD (Plain-Old-Data).
int __stdcall CompareInts(const byte* velem1, const byte* velem2)
{
int elem1 = *(int*)velem1;
int elem2 = *(int*)velem2;
if(elem1 < elem2)
return -1;
if(elem1 > elem2)
return 1;
return 0;
}
int __stdcall CompareStrings(const byte* velem1, const byte* velem2)
{
const char* elem1 = (char*)velem1;
const char* elem2 = (char*)velem2;
return strcmp(elem1, elem2);
}
int main(int argc, char* argv[])
{
int i;
int array[] = {5432, 4321, 3210, 2109, 1098};
cout << "Before sorting ints with Bubblesort\n";
for(i=0; i < 5; i++)
cout << array[i] << '\n';
Bubblesort((byte*)array, 5, sizeof(array[0]), &CompareInts);
cout << "After the sorting\n";
for(i=0; i < 5; i++)
cout << array[i] << '\n';
const char str[5][10] = {"estella",
"danielle",
"crissy",
"bo",
"angie"};
cout << "Before sorting strings with Quicksort\n";
for(i=0; i < 5; i++)
cout << str[i] << '\n';
Quicksort((byte*)str, 5, 10, &CompareStrings);
cout << "After the sorting\n";
for(i=0; i < 5; i++)
cout << str[i] << '\n';
return 0;
}
Calling Conventions
If you don't like the word __stdcall, you can use the CALLBACK macro, defined in windef.h, as