回调函数一般是作为其他函数的参数来使用的,实参通过函数名传进去,形参通过函数指针进行接收。
为了方便在括号内定义函数指针的形参,就需要为函数指针类型声明一个大写的别名,这样就可以像定义int i,一样,以“类型 变量名”的方式来定义函数指针了。 例如对于这样一个函数:
INT32 peernvsserch_process(void * _pData,PeerLook * _pNvs);就可以这样声明函数指针类型的别名:
typedef INT32 (*PEERNVSSERCH_PROCESS)(void * _pData,PeerLook * _pNvs);就可以这样定义形参:
NetClientSerchNvs(void* pData,PEERNVSSERCH_PROCESS CallBack) { }就可以这样使用实参:
NetClientSerchNvs( this, peernvsserch_process );PEERNVSSERCH_PROCESS就是函数指针类型别名 CallBack就是形参变量名 peernvsserch_process就是实参函数名 那么,peernvsserch_process就是回调函数。
有时候不声明别名,直接定义形参,例如:
NetClientSerchNvs( void* pData,,void (CALLBACK* peernvsserch_process)(void * _pData,PeerLook * _pNvs) );这样看起来就不是很清晰,但也是对的。加上CALLBACK,强调这是一个回调函数。
明白了以上内容,就好理解为什么要使用回调函数了。 一般,NetClientSerchNvs这个函数是SDK里边封装好的函数,这个函数里提供了某些信息,需要我们进行处理,SDK的厂家工程师就希望我们写一个处理函数,由它们来调用,在调用的时候,把这些信息以实参的形式传进我们写的这个函数里,供我们使用。写到这,好像使用消息响应会好一些,消息里也可以带参数啊。但是消息响应是单向的,如果SDK厂家的工程师需要使用我们的处理结果时怎么办呢?难道我们也发个消息,让他们去响应?这样一来一回,就麻烦多了,而使用函数调用就简单了,不仅可以传进内容,还可以传出内容。SDK的厂家工程师以函数指针的方式,直接为我们规定好这个函数的格式,可以有传进的参数,传出的参数,还可以有返回值。甚至还可以帮我们用typedef起好别名。只需要我们按照这个指针格式把这个函数写出来,然后再把这个函数名当做实参传进NetClientSerchNvs函数里供他们使用就行了。 我们按照他们的格式写出来的这个函数就叫回调函数。在上边的四步中,SDK厂家完成了第二步和第三步,我们完成了第一步和第四步。其中的PeerLook * _pNvs这个参数就是SDK厂家提供给我们的让我们处理的信息,这个例子里没有需要回传的信息。另外还有一个参数void * _pData,下边介绍这个参数。
回调函数要求定义成全局函数或者类的静态成员函数(我查了很多资料,都没有搞清楚为什么要这样),这样以来,就无法直接访问类的成员了,只能通过指针来访问,就像线程函数一样,必须传进去一个对话框指针(比如this),才能通过指针访问类的成员和控件。所以回调函数中必须有一个指针形参,类型一般是void,就是说什么指针都行,在回调函数内部再转化成与实参相对应的类型,这和线程函数是一致的。所以void * _pData这个参数就是传对话框的指针(this)用的,但是这个指针的实参是SDK在调用我们的回调函数时传进来的,那么SDK是怎么知道要传那个对话框的指针呢,当然是我们告诉他的,所以你可以看到在NetClientSerchNvs这个SDK函数的参数里,除了回调函数指针,还有一个void* pData指针,这就是我们要传进去的对话框的指针,在NetClientSerchNvs函数内部,它原封不动把这个指针又传给了回调函数。所以,在使用回调函数的地方,都会出现这个和回调函数参数里边一样的指针参数。你把this传进去,它再传给回调函数,然后回调函数再使用。
相对于全局函数来说,一般都建议把回调函数定义成静态成员函数,一是为了封装,二是防止函数重名,各个类中的函数可以重名,因为前边有类名区分。
综上所述,对于回调函数,我们需要做的步骤就是: 一、在源文件中根据typedef的格式定义一个成员函数,也就是回调函数。 二、在头文件中声明为静态函数。 三、使用函数名作为实参来使用回调函数,同时别忘了传递指针。 四、在回调函数开头,接收我们传递的指针。 五、编写回调函数内容。