一、什么是回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
百度百科:https://baike.baidu.com/item/%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0/7545973?fr=aladdin
回调函数机制:
1、定义一个函数(普通函数即可);
2、将此函数的地址注册给调用者;
3、特定的事件或条件发生时,调用者使用函数指针调用回调函数。
注:为什么要特定事件或条件发生?不应该随时都可以调用回调函数吗?
以下是回调函数的两种使用方式(简单理解):
1、
#include <stdio.h>
//typedef int(*callback)(int,int);
using callback = typedef int(*)(int,int);
int add(int a,int b,callback p){
return (*p)(a,b);
}
int add(int a,int b){
return a+b;
}
int main(int argc,char *args[]){
int res = add(4,2,add);
printf("%d\n",res);
return 0;
}
在这个例子中,可以看到,我们定义了一个callbak的函数指针,参数为两个int,返回值为int,通过调用函数地址来进行简单的相加运算。
2、
#include <stdio.h>
//typedef int (callBack)(const void *buffer,size_t size,char *p_out);
using callBack = int ()(const void *buffer,size_t size,char *p_out);
void callFunc(callBack *consume_bytes, char *p_out) {
printf("callFunc\n");
const void *buffer = NULL;
consume_bytes(buffer,0,p_out); //传入值可以随便填
}
int callBackFunc(const void *buffer, size_t size, char *p_out){
printf("callBackFunc\n");
memset(p_out,0x00,sizeof(char)*100);
strcpy(p_out,"encoderCallback:this is string.");
return 1;
}
int main(int argc,char *args[]){
char p_out[100];
callFunc(callBackFunc,p_out);
printf("%s\n",p_out);
return 0;
}
可以把回调函数和调用函数封装承类再调用。
回调函数还可以用于不同模块(dll)之间的数据交换时使用
3、函数指针
回调在C语言中是通过函数指针来实现的,通过将回调函数的地址传给被调函数从而实现回调。因此,要实现回调,必须首先定义函数指针,请看下面的例子:
void Func(char *s);// 函数原型
void (*pFunc) (char *);//函数指针
可以看出,函数的定义和函数指针的定义非常类似。
一般的化,为了简化函数指针类型的变量定义,提高程序的可读性,我们需要把函数指针类型自定义一下。
typedef void(*pcb)(char *); //pcb是一种类型
回调函数可以象普通函数一样被程序调用,但是只有它被当作参数传递给被调函数时才能称作回调函数
4、面向对象语言中的回调(C++)
回调函数必须是标准调用 __stdcall,用户调用也以以,不过意义不大。回调一定是__stdcall ,但是__stdcall不一定是回调
许多程序员都发现,利用MFC或者其它的C++应用编写回调函数是非常麻烦的,其根本原因是回调函数是基于C编程的Windows SDK的技术,不是针对C++的,程序员可以将一个C函数直接作为回调函数,但是如果试图直接使用C++的成员函数作为回调函数将发生错误,甚至编译就不能通过。通过查询资料发现,其错误是普通的C++成员函数都隐含了一个传递函数作为参数,亦即“this”指针,C++通过传递一个指向自身的指针给其成员函数从而实现程序函数可以访问C++的数据成员。这也可以理解为什么C++类的多个实例可以共享成员函数但是确有不同的数据成员。由于this指针的作用,使得将一个CALLBACK型的成员函数作为回调函数安装时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数安装失败。要解决这一问题的关键就是不让this指针起作用,通过采用以下两种典型技术可以解决在C++中使用回调函数所遇到的问题。这种方法具有通用性,适合于任何C++。
方法:
1). 不使用成员函数,直接使用普通C函数,
2). 使用静态成员函数,静态成员函数不使用this指针作为隐含参数,这样就可以作为回调函数了。
例:
class Test
{
public:
static void __stdcall getData(int iType, void * pBuffer, long int dLen);
};
void __stdcall Test::getData(int iType, void * pBuffer, long int dLen)
{
//do something
}
5、C++知识回顾之__stdcall、__cdcel和__fastcall三者的区别
__stdcall、__cdecl和__fastcall是三种函数调用协议,函数调用协议会影响函数参数的入栈方式、栈内数据的清除方式、编译器函数名的修饰规则等。
- 调用协议常用场合
-
- __stdcall:Windows API默认的函数调用协议。
- __cdecl:C/C++默认的函数调用协议。
- __fastcall:适用于对性能要求较高的场合。
- 函数参数入栈方式
-
- __stdcall:函数参数由右向左入栈。
- __cdecl:函数参数由右向左入栈。
- __fastcall:从左开始不大于4字节的参数放入CPU的ECX和EDX寄存器,其余参数从右向左入栈。
- 问题一:__fastcall在寄存器中放入不大于4字节的参数,故性能较高,适用于需要高性能的场合。
- 栈内数据清除方式
-
- __stdcall:函数调用结束后由被调用函数清除栈内数据。
- __cdecl:函数调用结束后由函数调用者清除栈内数据。
- __fastcall:函数调用结束后由被调用函数清除栈内数据。
- 问题一:不同编译器设定的栈结构不尽相同,跨开发平台时由函数调用者清除栈内数据不可行。
- 问题二:某些函数的参数是可变的,如printf函数,这样的函数只能由函数调用者清除栈内数据。
- 问题三:由调用者清除栈内数据时,每次调用都包含清除栈内数据的代码,故可执行文件较大。
- C语言编译器函数名称修饰规则
-
- __stdcall:编译后,函数名被修饰为“_functionname@number”。
- __cdecl:编译后,函数名被修饰为“_functionname”。
- __fastcall:编译后,函数名给修饰为“@functionname@nmuber”。
- 注:“functionname”为函数名,“number”为参数字节数。
- 注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。
- C++语言编译器函数名称修饰规则
-
- __stdcall:编译后,函数名被修饰为“?functionname@@YG******@Z”。
- __cdecl:编译后,函数名被修饰为“?functionname@@YA******@Z”。
- __fastcall:编译后,函数名被修饰为“?functionname@@YI******@Z”。
- 注:“******”为函数返回值类型和参数类型表。
- 注:函数实现和函数定义时如果使用了不同的函数调用协议,则无法实现函数调用。
- C语言和C++语言间如果不进行特殊处理,也无法实现函数的互相调用。
参考:
https://blog.youkuaiyun.com/yidu_fanchen/article/details/80513359
https://blog.youkuaiyun.com/songshu5555/article/details/46895231
https://www.cnblogs.com/yejianyong/p/7506465.html