C++回調函數(callback)的使用

什么是回調函數(callback)?
??? 模塊A有一個函數foo,它向模塊B傳遞foo的地址,然后在B里面發生某種事件(event)時,通過從A里面傳遞過來的foo的地址調用foo,通知A發生了什么事情,讓A作出相應反應。?那么我們就把foo稱為回調函數。
???
例子:
??????回調函數是一個很有用,也很重要的概念。當發生某種事件時,系統或其他函數將會自動調用你定義的一段函數。回調函數在windows編程使用的場合很多,?比如Hook回調函數:MouseProc,GetMsgProc以及EnumWindows,DrawState的回調函數等等,還有很多系統級的回調?過程。本文不準備介紹這些函數和過程,而是談談實現自己的回調函數的一些經驗。
??????之所以產生使用回調函數這個想法,是因為現在使用VC和Delphi混合編程,用VC寫的一個DLL程序進行一些時間比較長的異步工作,工作完成之后,需?要通知使用DLL的應用程序:某些事件已經完成,請處理事件的后續部分。開始想過使用同步對象,文件影射,消息等實現DLL函數到應用程序的通知,后來突?然想到可不可以在應用程序端先寫一個函數,等需要處理后續事宜的時候,在DLL里直接調用這個函數即可。???
???????于是就動手,寫了個回調函數的原形。在VC和?Delphi里都進行了測試
一:聲明回調函數類型。
????????vc版
???????????????typedef?int?(WINAPI?*PFCALLBACK)(int?Param1,int?Param2)?;
????????Delph版
???????????????PFCALLBACK?=?function(Param1:integer;Param2:integer):integer;stdcall;
????????實際上是聲明了一個返回值為int,傳入參數為兩個int的指向函數的指針。
????????由于C++和PASCAL編譯器對參數入棧和函數返回的處理有可能不一致,把函數類型用WINAPI(WINAPI宏展開就是__stdcall)或stdcall統一修飾。
二:聲明回調函數原形
????????聲明函數原形
???????vc版
????????????????int?WINAPI?CBFunc(int?Param1,int?Param2);
????????Delphi版
???????????function?CBFunc(Param1,Param2:integer):integer;stdcall;?????????????

???????以上函數為全局函數,如果要使用一個類里的函數作為回調函數原形,把該類函數聲明為靜態函數即可。


三:?回調函數調用調用者
??????????調用回調函數的函數我把它放到了DLL里,這是一個很簡單的VC生成的WIN32?DLL.并使用DEF文件輸出其函數名?TestCallBack。實現如下:
???????????????PFCALLBACK???gCallBack=0;
?????????????void?WINAPI?TestCallBack(PFCALLBACK?Func)
????????????{
???????????????????if(Func==NULL)return;
???????????????????gCallBack=Func;
???????????????????DWORD?ThreadID=0;
???????????????????HANDLE?hThread?=?CreateThread(???NULL,???NULL,???Thread1,????LPVOID(0),???????????&ThreadID?);
????????????????????return;
??????????????}
???????此函數的工作把傳入的?PFCALLBACK?Func參數保存起來等待使用,并且啟動一個線程。聲明了一個函數指針PFCALLBACK?gCallBack保存傳入的函數地址。
四:?回調函數如何被使用:
???????????TestCallBack函數被調用后,啟動了一個線程,作為演示,線程人為的進行了延時處理,并且把線程運行的過程打印在屏幕上.
本段線程的代碼也在DLL工程里實現
???????ULONG???WINAPI?Thread1(LPVOID?Param)
??????{
??????????????TCHAR?Buffer[256];
??????????????HDC?hDC?=?GetDC(HWND_DESKTOP);
??????????????int?Step=1;
??????????????MSG?Msg;

?? DWORD?StartTick;
?????????//一個延時循環
??????????????for(;Step<200;Step++)
??????????????{
?????????????????????????StartTick?=?GetTickCount();
???????????????????/*這一段為線程交出部分運行時間以讓系統處理其他事務*/
????????????????????????for(;GetTickCount()-StartTick<10;)
??????????????????????????{
??????????????????????????????????if(PeekMessage(&Msg,NULL,0,0,PM_NOREMOVE)?)
??????????????????????????????????{
????????????????????????????????????TranslateMessage(&Msg);
????????????????????????????????????DispatchMessage(&Msg);
????????????????????????????????????}
????????????????????????????}??

???????????????????????/*把運行情況打印到桌面,這是vcbear調試程序時最喜歡干的事情*/
????????????sprintf(Buffer,"Running?%04d",Step);
??????????????????????????if(hDC!=NULL)
???????????????????????????????????TextOut(hDC,30,50,Buffer,strlen(Buffer));
????????????????????}
?????????????????/*延時一段時間后調用回調函數*/??
?????????????????(*gCallback)(Step,1);
??????????????????/*結束*/
????????????????????::ReleaseDC?(HWND_DESKTOP,hDC);
???????????????????return?0;
???????}
五:萬事具備
?????????使用vc和Delphi各建立了一個工程,編寫回調函數的實現部分
????????VC版
??????int?WINAPI?CBFunc(int?Param1,int?Param2)
????????{
????????????????int?res=?Param1+Param2;
??????????????TCHAR?Buffer[256]="";
?????????????sprintf(Buffer,"callback?result?=?%d",res);
?????????????MessageBox(NULL,Buffer,"Testing",MB_OK);???//演示回調函數被調用
??????????????return?res;?
?}???
??????????Delphi版
???????????function?CBFunc(Param1,Param2:integer):integer;
???????????begin
???????????????????result:=?Param1+Param2;
???????????????????TForm1.Edit1.Text:=inttostr(result);?????/?/演示回調函數被調用
????????????end;
????????
????????使用靜態連接的方法連接DLL里的出口函數?TestCallBack,在工程里添加?Button(?對于Delphi的工程,還需要在Form1上放一個Edit控件,默認名為Edit1)。
?????????響應ButtonClick事件調用?TestCallBack
???????????????TestCallBack(CBFunc)?//函數的參數CBFunc為回調函數的地址
?????????函數調用創建線程后立刻返回,應用程序可以同時干別的事情去了。現在可以看到屏幕上不停的顯示字符串,表示dll里創建的線程運行正常。一會之后,線程延?時部分結束結束,vc的應用程序彈出MessageBox,表示回調函數被調用并顯示根據Param1,Param2運算的結果,Delphi的程序?edit控件里的文本則被改寫成Param1,Param2?的運算結果。
?????????可見使用回調函數的編程模式,可以根據不同的需求傳遞不同的回調函數地址,或者定義各種回調函數的原形(同時也需要改變使用回調函數的參數和返回值約?定),實現多種回調事件處理,可以使程序的控制靈活多變,也是一種高效率的,清晰的程序模塊之間的耦合方式。在一些異步或復雜的程序系統里尤其有用?--?你可以在一個模塊(如DLL)里專心實現模塊核心的業務流程和技術功能,外圍的擴展的功能只給出一個回調函數的接口,通過調用其他模塊傳遞過來的回調函數?地址的方式,將后續處理無縫地交給另一個模塊,隨它按自定義的方式處理。
???????本文的例子使用了在DLL里的多線程延時后調用回調函數的方式,只是為了突出一下回調函數的效果,其實只要是在本進程之內,都可以隨你高興可以把函數地址傳遞來傳遞去,當成回調函數使用。
????????這樣的編程模式原理非常簡單單一:就是把函數也看成一個指針一個地址來調用,沒有什么別的復雜的東西,僅僅是編程里的一個小技巧。至于回調函數模式究竟能為你帶來多少好處,就看你是否使用,如何使用這種編程模式了。


msdn上這么說的:
有關函數指針的知識
使用例子可以很好地說明函數指針的用法。首先,看一看?Win32?API?中的?EnumWindows?函數:
Declare?Function?EnumWindows?lib?"user32"?_
(ByVal?lpEnumFunc?as?Long,?_
ByVal?lParam?as?Long?)?As?Long
EnumWindows?是一個枚舉函數,它能夠列出系統中每一個打開的窗口的句柄。EnumWindows?的工作方式是重復地調用傳遞給它的第一個參數(lpEnumFunc,函數指針)。每當?EnumWindows?調用函數,EnumWindows?都傳遞一個打開窗口的句柄。
在代碼中調用?EnumWindows?時,可以將一個自定義函數作為第一個參數傳遞給它,用來處理一系列的值。例如,可以編寫一個函數將所有的值添加到一個列表框中,將?hWnd?值轉換為窗口的名字,以及其它任何操作!
為了表明傳遞的參數是一個自定義函數,在函數名稱的前面要加上?AddressOf?關鍵字。第二個參數可以是合適的任何值。例如,如果要把?MyProc?作為函數參數,可以按下面的方式調用?EnumWindows:
x?=?EnumWindows(AddressOf?MyProc,?5)
在調用過程時指定的自定義函數被稱為回調函數。回調函數(通常簡稱為“回調”)能夠對過程提供的數據執行指定的操作。
回調函數的參數集必須具有規定的形式,這是由使用回調函數的?API?決定的。關于需要什么參數,如何調用它們,請參閱?API?文檔。


我談一下自己對回調函數的一點理解,?不對的地方請指教.
?????我剛開始接觸回調時,?也是一團霧水.很多人解釋這個問題時,?總是拿API來舉例子,?本來菜鳥最懼怕的就是API,?^_^.?回調跟API沒有必然聯系.
?????其實回調就是一種利用函數指針進行函數調用的過程.
????
?????為什么要用回調呢?比如我要寫一個子模塊給你用,?來接收遠程socket發來的命令.當我接收到命令后,?需要調用你的主模塊的函數,?來進行相應的處理.但是我不知道你要用哪個函數來處理這個命令,???我也不知道你的主模塊是什么.cpp或者.h,?或者說,?我根本不用關心你在主模塊里怎么處理它,?也不應該關心用什么函數處理它......?怎么辦??
?????使用回調.
?????我在我的模塊里先定義回調函數類型,?以及回調函數指針.
?????typedef?void?(CALLBACK?*cbkSendCmdToMain)?(AnsiString?sCmd);
?????cbkSendCmdToMain?????SendCmdToMain;
?????這樣SendCmdToMain就是一個指向擁有一個AnsiString形參,?返回值為void的函數指針.
?????這樣,?在我接收到命令時,?就可以調用這個函數啦.

...
?????SendCmdToMain(sCommand);
?????...
?????但是這樣還不夠,?我得給一個接口函數(比如Init),?讓你在主模塊里調用Init來注冊這個回調函數.
?????在你的主模塊里,?可能這樣
?????void?CALLBACK?YourSendCmdFun(AnsiString?sCmd);???//聲明
?????...
?????void?CALLBACK?YourSendCmdFun(AnsiString?sCmd);???//定義
?????{
?????????ShowMessage(sCmd);
?????}
?????...
?????調用Init函數向我的模塊注冊回調.可能這樣:
?????Init(YourSendCmdFun,?...);
?????這樣,?預期目的就達到了.

?????需要注意一點,?回調函數一般都要聲明為全局的.?如果要在類里使用回調函數,?前面需要加上?static???,?其實也相當于全局的.


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/450685.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/450685.shtml
英文地址,請注明出處:http://en.pswp.cn/news/450685.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Hibernate JPA中@Transient、@JsonIgnoreProperties、@JsonIgnore、@JsonFormat、@JsonSerialize等注解解釋

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1、Transient Transient表示該屬性并非一個到數據庫表的字段的映射,ORM框架將忽略該屬性&#xff1b; 如果一個屬性并非數據庫表的字段…

可愛的rem

前端開發中&#xff0c;移動端的開發可以說是舉足輕重了&#xff0c;可是又面臨著不同設備尺寸和分辨率的尷尬點。今天[2018-09-16]臺風山竹登陸廣東&#xff0c;來勢洶洶&#xff0c;外出是不可能的了&#xff0c;那就宅著寫寫這篇小文章吧...原文請戳這里-談談rem單位 超長的…

kafka直連方式消費多個topic

一個消費者組可以消費多個topic&#xff0c;以前寫過一篇一個消費者消費一個topic的&#xff0c;這次的是一個消費者組通過直連方式消費多個topic,做了小測試&#xff0c;結果是正確的&#xff0c;通過查看zookeeper的客戶端&#xff0c;zookeeper記錄了偏移量 package day04 /…

100個經典的C語言算法

100個經典的C算法 C語言的學習要從基礎開始&#xff0c;這里是100個經典的算法 題目&#xff1a;古典問題&#xff1a;有一對兔子&#xff0c;從出生后第3個月起每個月都生一對兔子&#xff0c;小兔 子長到第三個月后每個月又生一對兔子&#xff0c;假如兔子都不死&#xff0c;…

MySQL常見面試題目詳解

文章目錄1. SQL1.1 介紹一下數據庫分頁1.2 介紹一下SQL中的聚合函數1.3 表跟表是怎么關聯的&#xff1f;1.4 說一說你對外連接的了解1.5 說一說數據庫的左連接和右連接1.6 SQL中怎么將行轉成列&#xff1f;1.7 談談你對SQL注入的理解1.8 將一張表的部分數據更新到另一張表&…

[轉]windows系統激活

原文鏈接主題&#xff1a;使用kms激活&#xff0c;可以直接使用命令來完成。 方法&#xff1a;在win10桌面狀態下&#xff0c;右擊windows徽標或按快捷鍵windowsx&#xff0c;點擊命令提示符&#xff08;管理員&#xff09; 用到的命令是slmgr&#xff0c;手動kms激活命令如下&…

jackson annotations注解詳解

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 官方WIKI&#xff1a;https://github.com/FasterXML/jackson-databind/wiki jackson 1.x和2.x版本的注解是放置在不同的包下的 1.x是在…

JS-for的衍生對象

在js中一般使用方法&#xff1a; 1.常規的for(var i0;i<length;i) 2.for-in:for(var item in list) 3.for of 描述&#xff1a;對應于一個對象的每個屬性&#xff0c;或一個數組的每個元素&#xff0c;執行一個或多個語句。 語法&#xff1a;for (variable in [object | ar…

浮點數在計算機中存儲方式

C語言和C#語言中&#xff0c;對于浮點類型的數據采用單精度類型&#xff08;float&#xff09;和雙精度類型(double)來存儲&#xff0c;float數據占用32bit,double數據占用64bit,我們在聲明一個變量float f 2.25f的時候&#xff0c;是如何分配內存的呢&#xff1f;如果胡亂分配…

操作系統面試題目詳解

文章目錄1.13 什么是協程&#xff1f;1.14 為什么協程比線程切換的開銷小&#xff1f;1.15 線程和進程的區別&#xff1f;1.16 進程切換為什么比線程更消耗資源&#xff1f;1.17 介紹一下進程之間的通信。1.18 介紹一下信號量。1.19 說說僵尸進程和孤兒進程。1.20 請介紹進程之…

(項目)在線教育平臺(六)

八、授課機構功能 1、模板繼承 如果幾個頁面的大體結構相同&#xff0c;可以使用繼承的方式來實現母版的重用性&#xff0c;也就是子版繼承母版的內容&#xff0c;既可以使用模板的內容&#xff0c;也可以重寫需要改變的地地方。 首先完成授課機構的頁面&#xff0c;通過頁面顯…

C語言 socket 編程學習

對于SOCKET在這里我不想究其歷史,我只想說其時它是一種進程通訊的方式,簡言之就是調用這個網絡庫的一些API函數就能實現分布在不同主機的相關進程之間的數據交換. SOCKET中首先我們要理解如下幾個定義概念: 一是IP地址:IP Address我想很容易理解,就是依照TCP/IP協議分配…

dependency 中的 classifier屬性

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 classifier元素用來幫助定義構件輸出的一些附屬構件。附屬構件與主構件對應&#xff0c;比如主構件是 kimi-app-2.0.0.jar 該項目可能還…

PHP超全局變量$_SERVER

$_SERVER 是一個包含了諸如頭信息(header)、路徑(path)、以及腳本位置(script locations)等等信息的數組。這個數組中的項目由 Web 服務器創建。不能保證每個服務器都提供全部項目&#xff1b;服務器可能會忽略一些&#xff0c;或者提供一些沒有在這里列舉出來的項目。 $_SERVE…

VC讀寫XML文件

1、安裝MSXML 4.0 SP2。在VC6中建立一個基于Dialog的工程。如圖&#xff1a; 在界面上放置3個編輯框、1個按鈕控件。其中屬性設置如下。 編輯框&#xff1a; IDCategoryVariable TypeVariable NameIDC_IDValueCStringm_strIdIDC_AUTHORValueCStringm_strAuthorIDC_TITLEValueCS…

XCode10 swift4.2 適配遇到的坑

以下是2018年10月23日更新 經過大約一個月的時間的適配&#xff0c;項目正式使用XCode10(以下簡稱為10 or XC10)大部分庫都升級為Swift4.2&#xff08;以下簡稱為 4.2 or S4.2&#xff09;&#xff0c;下面是適配過程中遇到的一些坑。 1. Swift4、Swift4.2混編 如果你對項目是小…

學生管理系統Java版

簡單的學生管理系統 主界面編寫&#xff1a; 1.用輸出語句完成主界面的編寫 2.用Scanner語句實現鍵盤的錄入 3.用swich語句完成操作的選擇 4.用循環完成再次回到主界面 代碼實現&#xff1a; while (true) {//1.用輸出語句完成主界面的編寫System.out.println("--------…

dubbo 配置文件詳解

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 一、dubbo常用配置 <dubbo:service/> 服務配置&#xff0c;用于暴露一個服務&#xff0c;定義服務的元信息&#xff0c;一個服務可…

ASP.NET Core 實戰:Linux 小白的 .NET Core 部署之路

一、前言 最近一段時間自己主要的學習計劃還是按照畢業后設定的計劃&#xff0c;自己一步步的搭建一個前后端分離的 ASP.NET Core 項目&#xff0c;目前也還在繼續學習 Vue 中&#xff0c;雖然中間斷了很長時間&#xff0c;好歹還是堅持下來了&#xff0c;嗯&#xff0c;看了看…

學以致用十三-----Centos7.2+python3+YouCompleteMe成功歷程

歷經幾天的摸索&#xff0c;趟過幾趟坑之后&#xff0c;終于完成YouCompleteMe的安裝配置。 今天同樣是個不能忘記的日子&#xff0c;國恥日&#xff0c;勿忘國恥。&#xff08;9.18&#xff09; 服務器安裝好&#xff0c;基本配置配置好后&#xff0c;開始安裝。 一、檢查服務…