This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

 

  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.

 

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.