This article presents a neat trick allowing you to use a method of any class as a callback.
In many cases, one layer of your SW needs to get services from higher layers. In order to not hurt the layering concept, the "callback" mechanism was invented: the layer which needs the service only knows (and actually declares) the prototype of the service required, not the actual service provider itself. The address of the function which will actually perform the required service is supplied at run-time, via some registration mechanism.
This has been used extensively in C, and even many Win32 APIs get such callback functions as parameters (e.g.
::EnumChildWindows()). The problem presents itself when C++ class methods are the functions you would like to perform the job, not "plain" functions as is the usual case.
The basic reason this won't work is the fact that the
this parameter in C++ is "hidden" -- it is not explicitly passed as any other "regular" argument. You cannot thus masquerade a C++ method as a plain C function.
Let's look at the type of a plain C function returning an
int and receiving two
typedef int (*FuncType)( int, int );
The C++ type of of a method in class
CMyClass doing the same is:
typedef int (*CMyClass::MethodType)( int, int );
For the reason stated above,
MethodType are not compatible. The compiler's type-checking system will yell at you for trying to convert between the two, and no kind of cast will help. None. No, not even
reinterpret_cast. In fact, some crazy attempts at static casting to "solve" this problem have been know to crash the VC++ compiler and cause internal errors.
Of course, the compiler is right about this: it should not be allowed, since even if you could pass the address of a method to where a regular function is needed, the caller will not be able to specify
this to the called method.
What can be done
Several approaches exist. The easiest is simply to avoid the problem altogether: this is sometimes even considered "pretty" C++ programming. For example: when an object wants three callbacks, it will declare an interface (a pure-abstract class with no members) and ask for an interface pointer instead of asking for the three callbacks. It is up to the implementer to implement the three methods, usually with the class-inside-class idiom, and sometimes using multiple inheritance and inheriting from the said interface (as is done in ATL, with all the
fooImpl template classes).
Another approach, which I like best, calls for writing small "wrapper" functions for the methods you want to pass as callbacks and passing the wrapper functions themselves as the callback. The wrapper functions are static members of the same class as the method you originally wanted to pass as callback. This requires that the prototype for the callback function gets, on top all the application-specific parameters, some kind of "Context" or "UserData" (X11/the Motif name) parameter, so the code registering the wrapper method can also register the value of
this to be later used by the static wrapper when calling the method.
Since these are important ideas (both the static wrapper function and the notion of "UserData" and "Context"), I intend to elaborate on these in future versions of this article should it prove popular enough (more than 0 people read it :)).
Finally, there's the "dirty" approach. I assume there's more than one way, but following is how I do it.
Two things need to be resolved: the first is how to get the address of the method into a
void* (or for that matter: any other type which does not have the word
CMyClass:: in it). The second thing is how to call the method (remember the
this issue?) once we do have the address.
The first problem is solved with a trick: no cast will work, because all of them do some sort of type-checking. What is the C/C++ construct which actually screams "no type checking done here"? 2 points to anyone who guessed "Ellipses". The compiler will not do any type checking on the actual parameters passed for an ellipses argument. This way, a really small function can be made to cast practically anything into a
The second problem, of how to call the method once we have its address, is solved by using a "caller thunk": a function whose job it is to call the method pointed to by a
void*, given the pointer and a value for
this. Most methods' calling convention is
__stdcall, which means amongst other things, that
this is passed in the
ECX register and that parameters are pushed on the stack from right to left. Using this knowledge, we can write a small function (in assembly) to do just that: invoke
__stdcall methods itself.
Things to note
The implementation of the "caller thunk" is not portable: it should be changed for every compiler, since not all compilers pass
ECX. (Heck, not all machines in the world even have
ECX :)). That's not too bad, though: I already have versions of this thunk for the ARM C++ compiler, for the TeakLite C++ compiler, and for the GNU ARM C++ compiler.
You need to bear in mind that the approach works for
__stdcall methods. If your method is explicitly declared to not be
__stdcall, or if it has a variable number of arguments (ellipses), the trick will not work. You will simply need to replace the caller thunk to something appropriate. Again -- not too hard.
Finally, please note what you are getting: you are taking the address of a method at link time, not at run-time. This means that late binding techniques (virtual function tables for virtual methods, for example) will not work. If you take the address of
CMyClass::f1(), and some class derives from
CMyClass and overrides
f1(), you will still call
If you think about it for a second, you will see that this is fine: you are losing nothing here. Since you gave the class name at compile time, it's the same as if you could directly call
CMyClass::f1() -- you don't expect that to be late-bound, now do you?
Using the "caller thunk" to call static methods and "regular" functions shouldn't work. Or should it? There's no real reason it shouldn't -- all it does is a regular call plus setting of
ECX. Since such functions don't use
ECX to get
this, there's no harm in setting it. Thus, all we need to make sure of is that these functions (unlike the default!) will use the
__stdcall calling convention. This is shown in the example.
Using the code
There is only one source file, demonstrating the trick. It contains a plethora of functions/methods which will be used as callbacks, a caller thunk for
__stdcall, a function to convert everything into a
void*, and the main driver.
I demonstrate a non-virtual method (
CMyClass::f1), two virtual methods (
CAnotherClass::f2), and a regular function (
MyFunc) as callback functions.
The function to convert everything into a
ToVoidStar(). The caller thunk is
CallThunk(). See if you can figure out why it's fine that it does not return any value! (Spoiler: it's fine since the return value is kept in
EAX by the called callback, and is not changed until we get to
None so far :)