回調1

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

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

//----------------------------------------------------------------------
又一篇關於回調函數的文章

回調函數,就是由你自己寫的。你需要調用另外一個函數,而這個函數的其中一個參數,就
是你的這個回調函數名。這樣,系統在必要的時候,就會調用你寫的回調函數,這樣你就可
以在回調函數裏完成你要做的事。

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   中,會調都是使用函數指針實現的。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章