VC中回調函數的用法


回調函數,就是由你自己寫的。你需要調用另外一個函數,而這個函數的其中一個參數,就

是你的這個回調函數名。這樣,系統在必要的時候,就會調用你寫的回調函數,這樣你就可

以在回調函數里完成你要做的事。

 示例下載:http://download.csdn.net/detail/zahxz/6750345

  前些天寫一個可編輯的ListCtrl類時,遇到這樣一個問題,在ListCtrl的指定格中創建了一個Button,創建過程我寫在

ListCtrlButtuon.cpp中,在對外提供的接中類CMMListCtrl中這樣調用:

void SetButtonEx(int iColumn, int iRow, int iIndex, int (*)(CWnd *pWnd, int iItem, int iSubItem));

這樣設計的思想是,當單擊Button時,相應的消息處理在調用者自己的類中,不須要改動CListCtrlButton類來響應用戶的具體處理,非常靈活.這一過程如上面所看到的,用到了回調函數.下面簡單做下總結:

 1.在ListCtrlButton.h頭部中定義回調函數指針:

   typedef int (*EditCallFunc)(CWnd *, int, int);

 2.在CListCtrlButton類中定義:

   EditCallFunc m_func;

 3.然后在OnClicked()消息響應中應用此函數:

   void CListCtrlButton::OnClicked()

   {  

    m_func(m_pParentList, m_iItem, m_iSubItem);

    OnEditEnd();

    }

  4.在我封裝的MMListCtrl接口類中這樣使用:

 // MMListCtrl.h中

  void SetButtonEx(int iColumn, int iRow, int iIndex, int (*)(CWnd *pWnd, int iItem, int iSubItem));

 

 //MMListCtrl.cpp中

/*********************************************************************

* 函數名稱:SetButtonEx

* 說明:將指定方格格設為Button,有關點擊Button后的相應操作應根據實際情況添加

* 入口參數:

* int iColumn       -- 指定格所在的列

* int iRow          -- 指定格所在的行

* int iIndex        -- 用於區分不同的Button (iIndex = 0, 1, 2,...)

* int (*fSetClickButton)(CWnd *, int , int)

                    --函數指針,指向實現函數

* 返回值:

* void              --

* 作者: Duanyx

* 時間: 2008-04-10 11:40:50

* 修改: 如果不用iIndex,采用二維數組存放如果行數超過一定值發生stack overflow

*********************************************************************/

 

void CMMListCtrl::SetButtonEx(int iColumn, int iRow, int iIndex, int (*fSetClickButton)(CWnd *, int, int))

{  

    if (m_ListCtrlButton[iIndex].m_hWnd == NULL)

    {

        m_ListCtrlButton[iIndex].CreateEx(this->GetParent(), this);

        m_ListCtrlButton[iIndex].Insert(iColumn, iRow);

        m_ListCtrlButton[iIndex].m_func = fSetClickButton;     

    }

    else

    {

        m_ListCtrlButton[iIndex].Insert(iColumn, iRow);

        m_ListCtrlButton[iIndex].m_func = fSetClickButton;

    }  

}

 

5.下面就是用戶類中調用的使用,假設已經聲明CMMListCtrl m_List;

在用戶類EditListCtrlDlg.h的頭部聲明:

int SetMyDlg2(CWnd *pWnd, int iItem, int iSubItem);

//EditListCtrlDlg.cpp中

BOOL CEditListCtrlDlg::OnInitDialog()

{

    CDialog::OnInitDialog();

 

       //注意最后一個參數為函數指針

       m_List.SetButtonEx(0, 0, 1, SetMyDlg);

}

 

//最后是此函數的實現

int SetMyDlg2(CWnd *pWnd, int iItem, int iSubItem)

{

    CString strText;

    COleTest dlg;

 

    if (dlg.DoModal() == IDOK)

    {

        strText = "dyx1024@gmail.com";

    }

    ((CListCtrl *)pWnd)->SetItemText(iItem, iSubItem, strText);

    return 0;

}

 

------------------------------------------------------------------------------------------------------

在書寫的過程中參考了網上好多資料,附於此處,以便讀者能更好的理解回調函數的使用.

 

參考資料一:

 

回調函數,就是由你自己寫的。你需要調用另外一個函數,而這個函數的其中一個參數,就

是你的這個回調函數名。這樣,系統在必要的時候,就會調用你寫的回調函數,這樣你就可

以在回調函數里完成你要做的事。

 

capVideoStreamCallback 這個回調函數,我沒有做過,看了一下Help,應該是通過發送消息

WM_CAP_SET_CALLBACK_VIDEOSTREAM,來設置的,或者調用宏capSetCallbackOnVideoStream

來完成的。這樣設定之后,系統在進行圖像捕捉的過程中,就會自動調用你寫的回調函數。

 

這個回調函數的函數體需要你自已來寫,然后在另一函數中調用,即是說:

LRESULT CALLBACK capVideoStreamCallback(HWND hWnd,LPVIDEOHDR lpVHdr)

{

 ........

}

//在另一函數中調用它(即以capVideoStreamCallback的地址作為一參數)

Function(1,......,capVideoStreamCallback,.....);

這就好像我們用定時器一樣,在設置定時器時需要為定時器設置一回調函數:

::SetTimer(m_hWnd,1,1000,(TIMERPROC)TMProc);這里的TMProc就是回調函數

 

 

模塊A有一個函數foo,它向模塊B傳遞foo的地址,然后在B里面發生某種事件(event)時,通過從A里面傳遞過來的foo的地址調用foo,通知A發生了什么事情,讓A作出相應反應。

    那么我們就把foo稱為回調函數。

 

    這個回調函數不是VFW.h中聲明的么,

    ----那是聲明了回調函數原型,是告訴你傳遞進來的回調函數必須和它定義的原型保持一致。

 

    為什么要自己寫函數體呢?

    ----比如在上面模塊B里面,它只知道當event發生時,向模塊A發出通知,具體怎么回應這個事件,不是B所關心的,也不是B所能預料到的。

    你站在A的角度上思考,當然要你自己作出對event的反應,也就是你要自己寫函數體。

 

    你如果明白了C++里面的函數指針,就很容易理解回調函數了。

 

"不知道系統調用后有什么結果,或者我怎么利用這個結果啊"

---如果你向系統傳遞一個回調函數地址,那么你的程序就相當於上面我說的模塊A,系統就相當於模塊B,系統只是調用你的函數,它根本不可能知道會有什么結果。

   你怎么利用這個結果,看你是怎么定義這個回調函數的。     

回調函數和回調機制是不同的概念,。,,函數是被調用的,但是回調機制在不同的語言中不都是以函數指針來實現的。。。。比如c#...一般的在windows api 中,會調都是使用函數指針實現的。。。

 

參考資料二:

回調函數是一個很有用,也很重要的概念。當發生某種事件時,系統或其他函數將會自動調用你定義的一段函數。回調函數在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里的多線程延時后調用回調函數的方式,只是為了突出一下回調函數的效果,其實只要是在本進程之內,都可以隨你高興可以把函數地址傳遞來傳遞去,當成回調函數使用。

 

 這樣的編程模式原理非常簡單單一:就是把函數也看成一個指針一個地址來調用,沒有什么別的復雜的東西,僅僅是編程里的一個小技巧。至於回調函數模式究竟能為你帶來多少好處,就看你是否使用,如何使用這種編程模式了。

 

  示例下載:http://download.csdn.net/detail/zahxz/6750345


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM