一:What?(什么是回调函数)
回调函数图文讲解
谓回调,就是客户程序C调用服务程序S中的某个函数A,然后S又在某个时候反过来调用C中的某个函数B,对于C来说,这个B便叫做回调函数。
例如Win32下的窗口过程函数就是一个典型的回调函数。
一般说来,C不会自己调用B,C提供B的目的就是让S来调用它,而且是C不得不提供。由于S并不知道C提供的B叫甚名谁,所以S会约定B的接
口规范(函数原型),然后由C提前通过S的一个函数R告诉S自己将要使用B函数,这个过程称为回调函数的注册,R称为注册函数。
再看看回调函数的庐山面目
下面的SmartOS中Zegbe通讯函数回调的注册 (由于是公司商业代码,所以不贴逻辑代码)
1 virtual void Register(TransportHandler handler, void* param = NULL) 2 { 3 if(handler) 4 { 5 _handler = handler; 6 _param = param; 7 8 if(!Opened) Open(); 9 } 10 else 11 { 12 _handler = NULL; 13 _param = NULL; 14 } 15 } 16 17 protected: 18 virtual bool OnOpen() { return true; } 19 virtual void OnClose() { } 20 virtual bool OnWrite(const byte* buf, uint len) = 0; 21 virtual uint OnRead(byte* buf, uint len) = 0; 22 23 // 是否有回调函数 24 bool HasHandler() { return _handler != NULL; } 25 26 // 引发数据到达事件 27 virtual uint OnReceive(byte* buf, uint len) 28 { 29 if(_handler) return _handler(this, buf, len, _param); 30 31 return 0; 32 }
这个是整个通讯流类里面的回调函数使用
下面举个例子说明回调函数的过程:
①: 比喻我打电话向你请教问题,当然是个难题
②: 你一时也想不出解决方法,我又不能拿着电话在那里傻等,于是我们约定:等你想出办法后打手机通知我,这样,我就挂掉电话办其它事情去了。 (注册回调函数)
③: XX分钟,我的手机响了,你兴高采烈的说问题已经搞定,应该如此这般处理。故事到此结束。 (回调函数操作)
其中,你后来打手机告诉我结果便是一个“回调”过程;我的手机号码必须在以前告诉你,这便是注册回调函数;我的手机号码应该有效并且手机能够接收到你的呼叫,这是回调函数必须符合接口规范。
回调函数的编程特点是:“异步+回调 ”
二 Why? ( 回调函数实现过程)
1 2 3 1 //def.h 4 2 #include <iostream> 5 3 #include <stdio.h> 6 4 using namespace std; 7 5 8 6 typedef enum 9 7 { 10 8 CB_MOVE = 0, // 11 9 CB_COMEBACK, // 12 10 CB_BUYEQUIIP, // 13 11 }cb_type; 14 12 15 13 typedef void(*cb_func)(void *); 16 14 17 15 class CCommu //模块类 18 16 { 19 17 public: 20 18 CCommu() 21 19 { 22 20 memset(func_list, 0, sizeof(cb_func) *(CB_BUYEQUIIP +1)); 23 21 memset(func_args, 0, sizeof(void *) *(CB_BUYEQUIIP +1)); 24 22 } 25 23 26 24 int reg_cb(cb_type type, cb_func func, void *args = NULL)//注册回调函数 27 25 { 28 26 if(type <= CB_BUYEQUIIP) 29 27 { 30 28 func_list[ type ] = func; 31 29 func_args[type] = args; 32 30 return 0; 33 31 } 34 32 } 35 33 public: 36 34 cb_func func_list[CB_BUYEQUIIP + 1] ; //函数指针数组 37 35 void * func_args[CB_BUYEQUIIP +1]; 38 36 }; 39 37 40 38 41 39 42 40 43 41 //Gamestart.h 44 42 #include "def.h" 45 43 46 44 class CGameStart 47 45 { 48 46 49 47 public: 50 48 CGameStart(); 51 49 ~CGameStart(); 52 50 void Init(); 53 51 void run(); 54 52 void Execute(); 55 53 56 54 //一些回调函数 57 55 void static Move(void *args); 58 56 void static Comeback(void *args); 59 57 void static Buyequip(void *args); 60 58 61 59 public: 62 60 CCommu *pCommu; 63 61 64 62 }; 65 63 66 64 67 65 68 66 //Gamestart.cpp 69 67 #include "Gamestart.h" 70 68 71 69 CGameStart::CGameStart():pCommu(NULL) 72 70 {} 73 71 74 72 void CGameStart::Init() //初始化的时候,注册回调函数 75 73 { 76 74 pCommu = new CCommu; 77 75 pCommu ->reg_cb(CB_MOVE, Move , this); 78 76 pCommu->reg_cb (CB_COMEBACK, Comeback,this ); 79 77 } 80 78 81 79 void CGameStart::run() 82 80 { 83 81 Init(); 84 82 } 85 83 86 84 void CGameStart::Execute() 87 85 { 88 86 cout<<"callback funciton is running"<<endl; 89 87 90 88 } 91 89 CGameStart::~CGameStart() 92 90 { 93 91 if(pCommu != NULL) 94 92 { 95 93 delete pCommu; 96 94 pCommu = NULL; 97 95 } 98 96 } 99 97 100 98 void CGameStart::Move(void *args) 101 99 { 102 100 CGameStart *pGame = (CGameStart *)args; 103 101 pGame -> Execute(); 104 102 } 105 103 void CGameStart::Comeback(void *args) 106 104 { 107 105 //char *str = (char *)args; 108 106 //cout << str <<endl; 109 107 } 110 108 111 109 112 110 113 111 114 112 115 113 116 114 //main.cpp 117 115 #include "Gamestart.h" 118 116 119 117 120 118 int main() 121 119 { 122 120 123 121 CGameStart *pGame = new CGameStart; 124 122 pGame -> run(); 125 123 if(pGame->pCommu->func_list[CB_MOVE] != NULL)//回调函数的触发 126 124 { 127 125 pGame->pCommu->func_list[CB_MOVE](pGame->pCommu->func_args[CB_MOVE]); 128 126 } 129 127 return 0; 130 128 }
时代在不断进步,SDK不再是古老的API接口,C++面向对象编程被广泛的用到各种库中,因此回调机制也可以采用C++的一些特性来实现。
通过前面的讲解,其实我们不难发现回调的本质便是:SDK定义出一套接口规范,应用程序按照规定实现它。这样一说是不是很简单,
想想我们C++中的继承,想想我们亲爱的抽象基类......于是,我们得到以下的代码:
1 /// sdk.h 2 #ifndef __SDK_H__ 3 #define __SDK_H__ 4 5 class Notifier // 回调类,应用程序需从此派生 6 { 7 public: 8 virtual ~Notifier() { } 9 virtual void got_answer(const char* answer) = 0; // 纯虚函数,用户必须实现它 10 }; 11 12 class Sdk // Sdk提供服务的类 13 { 14 public: 15 Sdk(Notifier* pnotifier); // 用户必须注册指向回调类的指针 16 void help_me(const char* question); 17 protected: 18 void do_it(); 19 protected: 20 Notifier* m_pnotifier; // 用于保存回调类的指针 21 }; 22 23 #define//__SDK_H__ 24 25 /// sdk.cpp 26 #include "sdk.h" 27 #include "windows.h" 28 #include <iostream> 29 using namespace std; 30 31 Sdk::Sdk(Notifier* pnotifier) : m_pnotifier(pnotifier) 32 { 33 } 34 35 void Sdk::help_me(const char* question) 36 { 37 cout << "help_me: " << question << endl; 38 do_it(); 39 } 40 41 void Sdk::do_it() 42 { 43 cout << "thinking..." << endl; 44 Sleep( 3000 ); 45 cout << "think out." << endl; 46 cout << "call him." << endl; 47 m_pnotifier->got_answer( "2." ); 48 } 49 50 /// app.cpp 51 #include "sdk.h" 52 53 class App : public Notifier // 应用程序实现一个从回调类派生的类 54 { 55 public: 56 App( const char* name ) : m_sdk(this), m_name(name) // 将this指针传入 57 { 58 } 59 void ask( const char* question ) 60 { 61 m_sdk.help_me( question ); 62 } 63 void got_answer( const char* answer ) // 实现纯虚接口 64 { 65 cout << m_name << " got_answer: " << answer << endl; 66 } 67 protected: 68 Sdk m_sdk; 69 const char* m_name; 70 }; 71 72 int main() 73 { 74 App app("ABC"); 75 app.ask( "1+1=?"); 76 return 0; 77 }
三: When? (什么时候使用回调)
如果你是SDK的使用者,一旦别人制定了回调机制,那么你被迫得使用回调函数,因此这个问题只对SDK设计者有意义。
从引入的目的看,回调大致分为三种:
1) SDK有消息需要通知应用程序,比如定时器被触发;
2) SDK的执行需要应用程序的参与,比如SDK需要你提供一种排序算法;
3) SDK的操作比较费时,但又不能让应用程序阻塞在那里,于是采用异步方式,让调用函数及时返回,SDK另起线程在后台执行操作,待操作完成后再将结果通知应用程序。
END!
参考部分网络资源
欢迎大家一起交流 ,分享程序员励志故事。 幸福的程序员 QQ群: