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.
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.
Now, follow the example that can be found in the
attached files. I have created a dynamic linked library called
sort.dll. It exports a type called CompareFunction:
typedef int (__stdcall *CompareFunction)(const byte*, const byte*);
which will be the type of your callback functions. It
also exports two methods, called Bubblesort() and Quicksort(), which
have the same prototype but provide different behavior by implementing
the sorting algorithms with the same name.
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:
The implementation of these two functions performs a
sorting of the array. But, each time there is a need to decide which of
two elements goes first, a callback is made to the function whose
address was passed as an argument. For the library writer, it doesn't
matter where that function is implemented, or how it is implemented.
All that matters it is that it takes the address of two elements (that
are the two be compared) and it returns one of the following values
(this is a contract between the library developers and its clients):
With this contract explicitly stated, the
implementation of the Bubblesort() function is this (for Quicksort(),
which a little bit more complicated, see the attached files).
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++)
{
if(1 == (*cmpFunc)(array+j*elem_size,
array+(j+1)*elem_size))
{
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).
On the client side, there must be a callback function
whose address is to be passed to the Bubblesort() function. As a simple
example, I have written a function that compares two integer values and
one that compares two strings:
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);
}
To put all these to a test, I have written this short
program. It passes an array with five elements to Bubblesort() or
Quicksort() along with the pointer to the callback functions.
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;
}
If I decide that I want the sorting to be done
descending (with the biggest element first), all I have to do is to
change the callback function code, or provide another that implements
the desired logic.
In the above code, you can see the word __stdcall
in the function's prototype. Because it starts with a double
underscore, it is, of course, a compiler-specific extension, more
exactly a Microsoft-specific one. Any compiler that supports
development of Win32-based applications must support this or an
equivalent one. A function that is marked with __stdcall uses
the standard calling convention so named because all Win32 API
functions (except the few that take variable arguments) use it.
Functions that follow the standard calling convention remove the
parameters from the stack before they return to the caller. This is the
standard convention for Pascal. But in C/C++, the calling convention is
that the caller cleans up the stack instead of the called function. To
enforce that a function uses the C/C++ calling convention, __cdecl must be used. Variable argument functions use the C/C++ calling convention.
Windows adopted the standard calling convention
(Pascal convention) because it reduces the size of the code. This was
very important in the early days of Windows, when it ran on systems
with 640 KB RAM.
If you don't like the word __stdcall, you can use the CALLBACK macro, defined in windef.h, as
#define CALLBACK __stdcall
or
#define CALLBACK PASCAL
where PASCAL is #defined as __stdcall.
You can read more about calling convention here: Calling Convetions in Microsoft Visual C++.
C++ Methods as Callback Functions
Because you probably write in C++, you want your callback function a method of a class. But, if you try this:
class CCallbackTester
{
public:
int CALLBACK CompareInts(const byte* velem1, const byte* velem2);
};
Bubblesort((byte*)array, 5, sizeof(array[0]),
&CCallbackTester::CompareInts);
with a MS compiler, you get this compilation error:
error C2664: 'Bubblesort' : cannot convert parameter 4 from 'int
(__stdcall CCallbackTester::*)(const unsigned char *,const unsigned
char *)' to 'int (__stdcall *)(const unsigned char *,const unsigned
char *)' There is no context in which this conversion is possible
That happens because non-static member functions have an additional parameter, pointer this (see this FAQ for more).
That obliges you to make the member function static. If that's not
acceptable, you can use several techniques to overcome that. Check the
following links to learn more.
Notices
The attached files contain two projects. SortingDLL is a
Win32 DLL project. The sort.dll output library exports the two sorting
functions, Bubblesort() and Quicksort(). The second project, SortDemo, is a Win32 Console Application that demonstrates how to use the sort.dll library. The output directory for both projects is Shared directory, where the following files can be found: sort.h, sort.dll, sort.lib, and SortDemo.exe.
Further References
About the Author
Marius Bancila is a Microsoft MVP
for VC++. He works as a software developer for a Norwegian-based
company. He is mainly focused on building desktop applications with MFC
and VC#. He keeps a blog at www.mariusbancila.ro/blog, focused on
Windows programming. In July 2007 together with two other Romanian MVPs
he created codexpert.ro, a community for Romanian C++/VC++ programmers.
Downloads
callbacks.zip - demo sample code