Click here to Skip to main content
Click here to Skip to main content

Using class methods as callbacks

By , 17 Apr 2004
Rate this:
Please Sign up or sign in to vote.

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 ints:

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 Smile | :) ).

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 Smile | :) ). 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 Smile | :)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Nitzan Shaked
Web Developer
Israel Israel
No Biography provided

Comments and Discussions

 
GeneralToVoidStar() ITS COOOOOOOOOL Pinmemberrdman3-Sep-06 11:14 
GeneralI also used this trick Pinmemberchief editor6-May-06 10:43 
GeneralNicely done but what about... Pinmembera1ring25-Oct-04 6:40 
QuestionWhy not pass pointer to class? Pinmemberravenspoint16-Jun-04 18:03 
GeneralGreat article Pinmembershlomim18-May-04 5:36 
Generalmake it generic Pinmembersclavel7-May-04 5:14 
GeneralMemories... PinmemberMasterDeep24-Apr-04 1:28 
GeneralFunctors instead of Function Pointers PinmemberDave Handley22-Apr-04 22:29 
GeneralNice trick, but... PinmemberdCp30321-Apr-04 4:30 
GeneralRe: Nice trick, but... PinmemberCalius21-Apr-04 5:25 
GeneralThis code frightens me, but it's interesting all the same PinmemberDon Clugston20-Apr-04 15:22 
GeneralRe: This code frightens me, but it's interesting all the same PinmemberCalius20-Apr-04 18:48 
GeneralRe: ARM thunk PinmemberCalius20-Apr-04 20:37 
GeneralGreat article - very interesting Pinmembermier20-Apr-04 5:13 
GeneralUse at your own peril! PinmemberMattyT19-Apr-04 14:38 
GeneralBasically right, but. PinmemberCalius19-Apr-04 18:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 18 Apr 2004
Article Copyright 2004 by Nitzan Shaked
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid