回调函数在C语言中用的非常频繁,它可以作用于结构体,但是到了C++中,类的成员函数是不能作为回调函数的。
详细说来,C的结构体中的函数都是函数指针,调用的时候一般不操作结构体内的数据否则要显式地在调用时传入
结构体的数据,即C中没有this这个概念。到了C++,假设类成员函数可以作为回调函数,那么这个函数对类成员
变量的操作作用于哪个实例呢?这显然是无法确定的,只有主动告知之。
关于this指针:看过C++类汇编代码的同志们都知道,在类实例调用它的成员函数时,call之前一定会有一步
mov ecx, [ebp-xxx]
这个指令就是根据栈基址找到改实例在栈中的地址,并放入ecx中;在被调用的函数中,所有对类成员变量的操作
都是根据这个ecx带进来的地址完成的。因此,这就是类的成员函数不能作为回调函数的根本原因,那我们给他个
ecx不就行了嘛~
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdarg.h>
- template </*return value*/typename RETYPE>
- class Function{
- public:
- Function(){
- this->objaddr = 0;
- this->callback = 0;
- }
- Function(int *objAddr, void *cb){
- objaddr = (int)objAddr;
- callback = cb;
- }
- Function(Function &input){
- this->objaddr = input.objaddr;
- this->callback = input.callback;
- }
- static RETYPE call(int addr, void *callback, int n){
- __asm{MOV ECX, addr}
- return ((int (__stdcall *)(int)) callback)(n);
- }
- //need to be extended
- static Function bind(void *objAddr, ...){
- va_list stack;
- va_start(stack, objAddr);
- Function ret((int *)objAddr, \
- (void *)va_arg(stack, int));
- return ret;
- }
- RETYPE operator()(int n){
- int addr = objaddr;
- void *cb = callback;
- return call(addr, cb, n);
- }
- private:
- int objaddr;
- void *callback;
- };
- class A{
- private:
- Function<int> cb;
- public:
- A(Function<int> callback)
- :cb(callback)
- {}
- void t(int n){
- cb(n);
- }
- };
- class B{
- public:
- int tt;
- B():tt(0){
- printf("B::tt inited as 0\n");
- }
- //private:
- int t(int n){
- tt += n;
- printf("B::tt changed to %d!\n", tt);
- return tt;
- }
- };
- int main(){
- B b1, b2;
- A a(Function<int>::bind(&b1, B::t));
- a.t(2);
- a.t(3);
-
- Function<int> fx;
- fx = Function<int>::bind(&b2, B::t);
- fx(9);
- return 0;
- }
我定义了一个模板Function,它的作用有三点:
1、把类成员函数的地址取出来,并转换成void *类型(通过进出栈来退化thiscall约定);见 bind;
2、保存该回调函数作用的实例地址;
3、提供调用该回调函数的方式(转换成stdcall,之前没有注意这点而被默认的cdecl搞得莫名其妙,因为它没有自动清栈);见 call。
使用这个模板的方法很简单,它就是一个变量而已~
记得跟我培训的C++大神说过,类成员函数是不能作为线程被创建的,原因应该也是这个;当然,用这个模板的话迂回一下也是可以实现的。
阅读(573) | 评论(0) | 转发(0) |