|
Light & Flexible C++ Callbacks
Submitted by |
Light and flexible C++ callbacks with type safety.
The code and mechanism presented are public domain.
The code presented here is a way to implement simple, flexible and above all
type-safe callbacks for objects in C++. The mechanism relies heavily on
templates for its type safety, mixed with a small amount of assembler 'glue' to
make it all work. Here goes...
Firstly, have you ever wanted to do this...?
class MyInterface {
public:
MyInterface() {
button1.setOnClick(&MyInterface::onClick, this);
}
void onClick(button1 & b) {
printf("User clicked button %d\n", b.getID());
}
protected:
Button button1;
}; |
Or even this...
class SocketHandler {
public:
SocketHandler() {
client.setOnRead(&SocketHandler::goRead, this);
}
void goRead(client * c, int bytes) {
char * data = c-readData(bytes);
}
protected:
Socket client;
}; |
Those of you familiar with function pointers and calling conventions will know
that for the above two scenarios to be realistic, we'd need a 'thiscall'
keyword. Which we, of course, do not have. Additionally, for anything other than
the most basic of callback systems we'd need some kind of switch mechanism to
shift all the events to their targets and so forth, coupled with a rigorous
target-object inheritance scheme.
However, in this system, the source objects and the target objects for a
callback need not know about one another. Nor do we need to worry about hundreds
of lines of support code; just a simple support class paradigm:
class ThisCall0 {
public:
ThisCall0() {
thisp = funcp = 0;
}
template<class T
void set(void (T::*func)(), T * t) {
unsigned long tp, fp;
_asm {
mov eax, t
mov tp, eax
mov eax, func
mov fp, eax
}
thisp = tp; funcp = fp;
}
void call() {
if(!thisp || !funcp)
return;
unsigned long thisp = this-thisp;
unsigned long funcp = this-funcp;
_asm {
mov ecx, thisp
call funcp
}
}
protected:
unsigned long thisp;
unsigned long funcp;
}; |
The above class will take the address of a member function on an object, and
using templates for safety will strip the compile-time information by way of a
cheeky assembler fiddle. See the 'ThisCall0::set' function above; It is designed
to load the long value of 'func' into 'fp', and the long value of 't' into 'tp'.
As you can see, 'func' and 't' are both template types, and so manipulation of
these variables is limited by the enforced template safety information carried
therein. So, in order to extract the pure pointer values from these variables a
small section of assembler is employed. In turn the values are placed in 'eax',
and then from 'eax' to their respective unsigned long stack variables.
Now calling the member function is no more difficult than loading 'ecx' (the
'this pointer' register) with the value of 'thisp', and then calling the code at
the address stored in 'funcp'.
The code below is an example of this in action:
MyClass c; // has a member function declared as 'void func(int a, int b)'
ThisCall2<int, int caller;
int main() {
caller.set(&MyClass::func, &c);
caller.call(1, 2);
return 0;
} |
Notice the use of a ThisCall2 class, and the two template arguments. This is to
ensure the type safety of calls to the target member function. The 2 after the
name of the caller class is to show that this type of caller requires two
arguments. It is declared as below:
template<typename A, typename B
class ThisCall2 {
public:
ThisCall2() {
thisp = funcp = 0;
}
template<class T
void set(void (T::*func)(A a, B b), T * t) {
unsigned long tp, fp;
_asm {
mov eax, t
mov tp, eax
mov eax, func
mov fp, eax
}
thisp = tp; funcp = fp;
}
void call(A a, B b) {
if(!thisp || !funcp)
return;
unsigned long thisp = this-thisp;
unsigned long funcp = this-funcp;
unsigned long aa, ab;
_asm {
mov eax, a
mov aa, eax
mov eax, b
mov ab, eax
mov ecx, thisp
push ab
push aa
call funcp
}
}
protected:
unsigned long thisp;
unsigned long funcp;
}; |
The difference between ThisCall0 and ThisCall2 (and other ThisCallX classes) is
the presence of the push operations in the call function above. These are
concerned with pushing the pure address values of the parameters onto the stack
ready for the thiscall. Notice the symmetry between the template arguments that
define the class, and the template arguments on the call function. This is again
to ensure type safety.
Finally, having described the mechanisms used, it is useful to describe how to
wrap the system to hide the ThisCaller class(s). Below is a stylised example.
// Button Header File
#include "thiscaller.h"
class Button {
public:
Button();
void clicked();
//--Events-----------------------------------------
template<class T
void setOnClick(void (T::*func)(Button&), T * t) {
caller.set(func, t);
}
//-------------------------------------------------
protected:
ThisCall1<Button& caller;
}; |
// Button Source File
Button::Button() {
//init my button.
}
void Button::clicked() {
caller.call(*this);
} |
Included is the aforementioned "thiscaller.h" header file. This file contains
five ThisCall classes to deal with 0, 1, 2, 3 and 4 parameter callbacks. Notice
none will handle return values, this is left as an exercise for the reader :).
|
Download Associated File: thiscaller.h (3,451 bytes)
class ThisCall0 {
public:
ThisCall0() {
thisp = funcp = 0;
}
template<class T>
void set(void (T::*func)(), T * t) {
unsigned long tp, fp;
_asm {
mov eax, t
mov tp, eax
mov eax, func
mov fp, eax
}
thisp = tp; funcp = fp;
}
void call() {
if(!thisp || !funcp)
return;
unsigned long thisp = this->thisp;
unsigned long funcp = this->funcp;
_asm {
mov ecx, thisp
call funcp
}
}
protected:
unsigned long thisp;
unsigned long funcp;
};
//----------------------------------------------------------------------------
template<typename A>
class ThisCall1 {
public:
ThisCall1() {
thisp = funcp = 0;
}
template<class T>
void set(void (T::*func)(A a), T * t) {
unsigned long tp, fp;
_asm {
mov eax, t
mov tp, eax
mov eax, func
mov fp, eax
}
thisp = tp; funcp = fp;
}
void call(A a) {
if(!thisp || !funcp)
return;
unsigned long thisp = this->thisp;
unsigned long funcp = this->funcp;
unsigned long aa;
_asm {
mov eax, a
mov aa, eax
mov ecx, thisp
push aa
call funcp
}
}
protected:
unsigned long thisp;
unsigned long funcp;
};
//----------------------------------------------------------------------------
template<typename A, typename B>
class ThisCall2 {
public:
ThisCall2() {
thisp = funcp = 0;
}
template<class T>
void set(void (T::*func)(A a, B b), T * t) {
unsigned long tp, fp;
_asm {
mov eax, t
mov tp, eax
mov eax, func
mov fp, eax
}
thisp = tp; funcp = fp;
}
void call(A a, B b) {
if(!thisp || !funcp)
return;
unsigned long thisp = this->thisp;
unsigned long funcp = this->funcp;
unsigned long aa, ab;
_asm {
mov eax, a
mov aa, eax
mov eax, b
mov ab, eax
mov ecx, thisp
push ab
push aa
call funcp
}
}
protected:
unsigned long thisp;
unsigned long funcp;
};
//----------------------------------------------------------------------------
template<typename A, typename B, typename C>
class ThisCall3 {
public:
ThisCall3() {
thisp = funcp = 0;
}
template<class T>
void set(void (T::*func)(A a, B b, C c), T * t) {
unsigned long tp, fp;
_asm {
mov eax, t
mov tp, eax
mov eax, func
mov fp, eax
}
thisp = tp; funcp = fp;
}
void call(A a, B b, C c) {
if(!thisp || !funcp)
return;
unsigned long thisp = this->thisp;
unsigned long funcp = this->funcp;
unsigned long aa, ab, ac;
_asm {
mov eax, a
mov aa, eax
mov eax, b
mov ab, eax
mov eax, c
mov ac, eax
mov ecx, thisp
push ac
push ab
push aa
call funcp
}
}
protected:
unsigned long thisp;
unsigned long funcp;
};
//----------------------------------------------------------------------------
template<typename A, typename B, typename C, typename D>
class ThisCall4 {
public:
ThisCall4() {
thisp = funcp = 0;
}
template<class T>
void set(void (T::*func)(A a, B b, C c, D d), T * t) {
unsigned long tp, fp;
_asm {
mov eax, t
mov tp, eax
mov eax, func
mov fp, eax
}
thisp = tp; funcp = fp;
}
void call(A a, B b, C c, D d) {
if(!thisp || !funcp)
return;
unsigned long thisp = this->thisp;
unsigned long funcp = this->funcp;
unsigned long aa, ab, ac, ad;
_asm {
mov eax, a
mov aa, eax
mov eax, b
mov ab, eax
mov eax, c
mov ac, eax
mov eax, d
mov ad, eax
mov ecx, thisp
push ad
push ac
push ab
push aa
call funcp
}
}
protected:
unsigned long thisp;
unsigned long funcp;
}; |
|
The zip file viewer built into the Developer Toolbox made use
of the zlib library, as well as the zlibdll source additions.
|