[注:摘自《深入浅出MFC》第6章浅出MFC程序设计(P.298)]
首先我要很快地解释一下什么是callback函数。凡是由你设计而却由Windows系统调用的函数,统称为callback函数。这些函数都有一定的类型,以配合Windows的调用操作。
某些Windows API函数会要求以callback函数作为其参数之一,这些API,例如SetTimer、LineDDA、EnumObjects。通常这种API会在进行某种行为之后或满足某种状态之时调用该callback函数。下面示范的EnumObjects是在发现某个Device Context的GDI object符合我们的指定类型时,调用callback函数。
好,现在我们要讨论的是,什么函数有资格在C++程序中作为callback函数?这个问题的背后是:C++程序中的callback函数有什么特别的吗?为什么要特别提出讨论?
是的,特别之处在于,C++编译器为类成员函数多准备了一个隐藏参数(在程序代码中看不到),这使得函数类型与Windows callback函数的默认类型不符。
假设我们有一个CMyClass如下:
class CMyClass { private: int nCount; int CALLBACK _export EnumObjectsProc(LPSTRlpLogObject, LPSTR lpData); public: void enumIt(CDC& dc); }
void CMyClass::enumIt(CDC& dc) { // 注册callback函数 dc.EnumObjects(OBJ_BRUSH, EnumObjectsProc, NULL); } C++编译器针对CMyClass::enumIt实际做出来的代码相当于: void CMyClass::enumIt(CDC& dc) { CDC::EnumObjects(OBJ_BRUSH, EnumObjectsProc, NULL, (CDC *)&dc); }
你所看到的最后一个参数,(CDC *)&dc,其实就是this指针,类成员函数靠着this指针才得以抓到正确的数据。你要知道,内存中只会有一份类成员函数,但却可能有许多份类的成员变量——每个对象拥有一份。
C++以隐含的this指针指出正确的对象。当你这么做:
nCount = 0;
其实是:
this->nCount = 0;
基于相同的道理,上例中的EnumObjectdsProc既然是一个成员函数,C++编译器也会为它多准备一个隐藏参数。
好,问题就出在这个隐藏参数。callback函数是给Windows调用的,Windows并不借助任何对象调用这个函数,也就没有this指针给callback函数,于是导致堆栈中有一个随机变量会成为指针,而其结果当然是程序的崩溃了。
要把某个函数用作callback函数,这必须告诉C++编译器,不要让this指针作为该函数的最后一个参数。采用以下两个方法可以做到这一点:
1. 不要使用类的成员函数(也就是说,要使用全局函数)作为callback函数。
2. 使用static成员函数。也就是在函数前面加上static修饰词。
第一种做法相当于在C语言中使用callback函数。第二种做法比较接近OO的精神。
我想更进一步提醒你的是,C++中的static成员函数特性是,即使对象还没有产生,static成员也已经存在(函数或变量都如此)。换句话说,对象还没有产生之前你已经可以调用类的static函数或使用类的static变量了。请参阅第2章。
也就是说,凡声明为static的东西(不管函数或变量)都并不和对象结合在一起,它们是类的一部分,不属于对象。