玩弄C++:回调一个类成员函数

回调函数在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++大神说过,类成员函数是不能作为线程被创建的,原因应该也是这个;当然,用这个模板的话迂回一下也是可以实现的。
posted @ 2012-06-16 18:56  cason_ai  阅读(887)  评论(0编辑  收藏  举报