Introduction
This article presents a neat trick allowing you to use a method of any class as a callback.
The need
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.
Theory
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 int
s:
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, FuncType
and 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.
The idea
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 void*
.
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 this
in 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 CMyClass::f1()
.
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 (CMyClass::f2
and CAnotherClass::f2
), and a regular function (MyFunc
) as callback functions.
The function to convert everything into a void*
is 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 CallThunk
's caller).
History
None so far :)