• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            一,回調(diào)函數(shù)

            我們經(jīng)常在C++設計時通過使用回調(diào)函數(shù)可以使有些應用(如定時器事件回調(diào)處理、用回調(diào)函數(shù)記錄某操作進度等)變得非常方便和符合邏輯,那么它的內(nèi)在機制如何呢,怎么定義呢?它和其它函數(shù)(比如鉤子函數(shù))有何不同呢?

            使用回調(diào)函數(shù)實際上就是在調(diào)用某個函數(shù)(通常是API函數(shù))時,將自己的一個函數(shù)(這個函數(shù)為回調(diào)函數(shù))的地址作為參數(shù)傳遞給那個函數(shù)。

            而那個函數(shù)在需要的時候,利用傳遞的地址調(diào)用回調(diào)函數(shù),這時你可以利用這個機會在回調(diào)函數(shù)中處理消息或完成一定的操作。至于如何定義回調(diào)函數(shù),跟具體使用的API函數(shù)有關,一般在幫助中有說明回調(diào)函數(shù)的參數(shù)和返回值等。C++中一般要求在回調(diào)函數(shù)前加CALLBACK(相當于FAR PASCAL),這主要是說明該函數(shù)的調(diào)用方式。

            至于鉤子函數(shù),只是回調(diào)函數(shù)的一個特例。習慣上把與SetWindowsHookEx函數(shù)一起使用的回調(diào)函數(shù)稱為鉤子函數(shù)。也有人把利用VirtualQueryEx安裝的函數(shù)稱為鉤子函數(shù),不過這種叫法不太流行。

            也可以這樣,更容易理解:回調(diào)函數(shù)就好像是一個中斷處理函數(shù),系統(tǒng)在符合你設定的條件時自動調(diào)用。為此,你需要做三件事:

            1.       聲明;

            2.       定義;

            3.       設置觸發(fā)條件,就是在你的函數(shù)中把你的回調(diào)函數(shù)名稱轉(zhuǎn)化為地址作為一個參數(shù),以便于系統(tǒng)調(diào)用。

            聲明和定義時應注意:回調(diào)函數(shù)由系統(tǒng)調(diào)用,所以可以認為它屬于WINDOWS系統(tǒng),不要把它當作你的某個類的成員函數(shù)。

             

            二,回調(diào)函數(shù)、消息和事件例程


                調(diào)用(calling)機制從匯編時代起已經(jīng)大量使用:準備一段現(xiàn)成的代碼,調(diào)用者可以隨時跳轉(zhuǎn)至此段代碼的起始地址,執(zhí)行完后再返回跳轉(zhuǎn)時的后續(xù)地址。CPU為此準備了現(xiàn)成的調(diào)用指令,調(diào)用時可以壓棧保護現(xiàn)場,調(diào)用結(jié)束后從堆棧中彈出現(xiàn)場地址,以便自動返回。借堆棧保護現(xiàn)場真是一項絕妙的發(fā)明,它使調(diào)用者和被調(diào)者可以互不相識,于是才有了后來的函數(shù)和構件。

                此調(diào)用機制并非完美?;卣{(diào)函數(shù)就是一例。函數(shù)之類本是為調(diào)用者準備的美餐,其烹制者應對食客了如指掌,但實情并非如此。例如,寫一個快速排序函數(shù)供他人調(diào)用,其中必包含比較大小。麻煩來了:此時并不知要比較的是何類數(shù)據(jù)--整數(shù)、浮點數(shù)、字符串?于是只好為每類數(shù)據(jù)制作一個不同的排序函數(shù)。更通行的辦法是在函數(shù)參數(shù)中列一個回調(diào)函數(shù)地址,并通知調(diào)用者:君需自己準備一個比較函數(shù),其中包含兩個指針類參數(shù),函數(shù)要比較此二指針所指數(shù)據(jù)之大小,并由函數(shù)返回值說明比較結(jié)果。排序函數(shù)借此調(diào)用者提供的函數(shù)來比較大小,借指針傳遞參數(shù),可以全然不管所比較的數(shù)據(jù)類型。被調(diào)用者回頭調(diào)用調(diào)用者的函數(shù)(夠咬嘴的),故稱其為回調(diào)(callback)。

                回調(diào)函數(shù)使程序結(jié)構亂了許多。Windows API 函數(shù)集中有不少回調(diào)函數(shù),盡管有詳盡說明,仍使初學者一頭霧水??峙逻@也是無奈之舉。

            無論何種事物,能以樹形結(jié)構單向描述畢竟讓人舒服些。如果某家族中孫輩又是某祖輩的祖輩,恐怕無人能理清其中的頭緒。但數(shù)據(jù)處理之復雜往往需要構成網(wǎng)狀結(jié)構,非簡單的客戶/服務器關系能窮盡。

                Windows 系統(tǒng)還包含著另一種更為廣泛的回調(diào)機制,即消息機制。消息本是 Windows 的基本控制手段,乍看與函數(shù)調(diào)用無關,其實是一種變相的函數(shù)調(diào)用。發(fā)送消息的目的是通知收方運行一段預先準備好的代碼,相當于調(diào)用一個函數(shù)。消息所附帶的 WParam 和 LParam 相當于函數(shù)的參數(shù),只不過比普通參數(shù)更通用一些。應用程序可以主動發(fā)送消息,更多情況下是坐等 Windows 發(fā)送消息。一旦消息進入所屬消息隊列,便檢感興趣的那些,跳轉(zhuǎn)去執(zhí)行相應的消息處理代碼。操作系統(tǒng)本是為應用程序服務,由應用程序來調(diào)用。而應用程序一旦啟動,卻要反過來等待操作系統(tǒng)的調(diào)用。這分明也是一種回調(diào),或者說是一種廣義回調(diào)。其實,應用程序之間也可以形成這種回調(diào)。假如進程 B 收到進程 A 發(fā)來的消息,啟動了一段代碼,其中又向進程 A 發(fā)送消息,這就形成了回調(diào)。這種回調(diào)比較隱蔽,弄不好會搞成遞歸調(diào)用,若缺少終止條件,將會循環(huán)不已,直至把程序搞垮。若是故意編寫成此遞歸調(diào)用,并設好終止條件,倒是很有意思。但這種程序結(jié)構太隱蔽,除非十分必要,還是不用為好。

                利用消息也可以構成狹義回調(diào)。上面所舉排序函數(shù)一例,可以把回調(diào)函數(shù)地址換成窗口 handle。如此,當需要比較數(shù)據(jù)大小時,不是去調(diào)用回調(diào)函數(shù),而是借 API 函數(shù) SendMessage 向指定窗口發(fā)送消息。收到消息方負責比較數(shù)據(jù)大小,把比較結(jié)果通過消息本身的返回值傳給消息發(fā)送方。所實現(xiàn)的功能與回調(diào)函數(shù)并無不同。當然,此例中改為消息純屬畫蛇添腳,反倒把程序搞得很慢。但其他情況下并非總是如此,特別是需要異步調(diào)用時,發(fā)送消息是一種不錯的選擇。假如回調(diào)函數(shù)中包含文件處理之類的低速處理,調(diào)用方等不得,需要把同步調(diào)用改為異步調(diào)用,去啟動一個單獨的線程,然后馬上執(zhí)行后續(xù)代碼,其余的事讓線程慢慢去做。一個替代辦法是借 API 函數(shù) PostMessage 發(fā)送一個異步消息,然后立即執(zhí)行后續(xù)代碼。這要比自己搞個線程省事許多,而且更安全。

                如今我們是活在一個 object 時代。只要與編程有關,無論何事都離不開 object。但 object 并未消除回調(diào),反而把它發(fā)揚光大,弄得到處都是,只不過大都以事件(event)的身份出現(xiàn),鑲嵌在某個結(jié)構之中,顯得更正統(tǒng),更容易被人接受。應用程序要使用某個構件,總要先弄清構件的屬性、方法和事件,然后給構件屬性賦值,在適當?shù)臅r候調(diào)用適當?shù)臉嫾椒ǎ€要給事件編寫處理例程,以備構件代碼來調(diào)用。何謂事件?它不過是一個指向事件例程的地址,與回調(diào)函數(shù)地址沒什么區(qū)別。

                不過,此種回調(diào)方式比傳統(tǒng)回調(diào)函數(shù)要高明許多。首先,它把讓人不太舒服的回調(diào)函數(shù)變成一種自然而然的處理例程,使編程者頓覺氣順。再者,地址是一個危險的東西,用好了可使程序加速,用不好處處是陷阱,程序隨時都會崩潰?,F(xiàn)代編程方式總是想法把地址隱藏起來(隱藏比較徹底的如 VB 和 Java),其代價是降低了程序效率。事件例程(?)使編程者無需直接操作地址,但并不會使程序減速。
            (例程似乎是進程的臺灣翻譯。)

             

            三,精妙比喻:回調(diào)函數(shù)還真有點像您隨身帶的BP機:告訴別人號碼,在它有事情時Call您。

                   回調(diào)用于層間協(xié)作,上層將本層函數(shù)安裝在下層,這個函數(shù)就是回調(diào),而下層在一定條件下觸發(fā)回調(diào),例如作為一個驅(qū)動,是一個底層,他在收到一個數(shù)據(jù)時,除了完成本層的處理工作外,還將進行回調(diào),將這個數(shù)據(jù)交給上層應用層來做進一步處理,這在分層的數(shù)據(jù)通信中很普遍。其實回調(diào)和API非常接近,他們的共性都是跨層調(diào)用的函數(shù)。但區(qū)別是API是低層提供給高層的調(diào)用,一般這個函數(shù)對高層都是已知的;而回調(diào)正好相反,他是高層提供給底層的調(diào)用,對于低層他是未知的,必須由高層進行安裝,這個安裝函數(shù)其實就是一個低層提供的API,安裝后低層不知道這個回調(diào)的名字,但它通過一個函數(shù)指針來保存這個回調(diào),在需要調(diào)用時,只需引用這個函數(shù)指針和相關的參數(shù)指針。    其實:回調(diào)就是該函數(shù)寫在高層,低層通過一個函數(shù)指針保存這個函數(shù),在某個事件的觸發(fā)下,低層通過該函數(shù)指針調(diào)用高層那個函數(shù)。

             


                軟件模塊之間總是存在著一定的接口,從調(diào)用方式上,可以把他們分為三類:同步調(diào)用、回調(diào)和異步調(diào)用。同步調(diào)用是一種阻塞式調(diào)用,調(diào)用方要等待對方執(zhí)行完畢才返回,它是一種單向調(diào)用;回調(diào)是一種雙向調(diào)用模式,也就是說,被調(diào)用方在接口被調(diào)用時也會調(diào)用對方的接口;異步調(diào)用是一種類似消息或事件的機制,不過它的調(diào)用方向剛好相反,接口的服務在收到某種訊息或發(fā)生某種事件時,會主動通知客戶方(即調(diào)用客戶方的接口)?;卣{(diào)和異步調(diào)用的關系非常緊密,通常我們使用回調(diào)來實現(xiàn)異步消息的注冊,通過異步調(diào)用來實現(xiàn)消息的通知。同步調(diào)用是三者當中最簡單的,而回調(diào)又常常是異步調(diào)用的基礎。
               
                對于不同類型的語言(如結(jié)構化語言和對象語言)、平臺(Win32、JDK)或構架(CORBA、DCOM、WebService),客戶和服務的交互除了同步方式以外,都需要具備一定的異步通知機制,讓服務方(或接口提供方)在某些情況下能夠主動通知客戶,而回調(diào)是實現(xiàn)異步的一個最簡捷的途徑。

                對于一般的結(jié)構化語言,可以通過回調(diào)函數(shù)來實現(xiàn)回調(diào)。回調(diào)函數(shù)也是一個函數(shù)或過程,不過它是一個由調(diào)用方自己實現(xiàn),供被調(diào)用方使用的特殊函數(shù)。

                在面向?qū)ο蟮恼Z言中,回調(diào)則是通過接口或抽象類來實現(xiàn)的,我們把實現(xiàn)這種接口的類成為回調(diào)類,回調(diào)類的對象成為回調(diào)對象。對于象C++或Object Pascal這些兼容了過程特性的對象語言,不僅提供了回調(diào)對象、回調(diào)方法等特性,也能兼容過程語言的回調(diào)函數(shù)機制。

                Windows平臺的消息機制也可以看作是回調(diào)的一種應用,我們通過系統(tǒng)提供的接口注冊消息處理函數(shù)(即回調(diào)函數(shù)),從而實現(xiàn)接收、處理消息的目的。由于Windows平臺的API是用C語言來構建的,我們可以認為它也是回調(diào)函數(shù)的一個特例。

                對于分布式組件代理體系CORBA,異步處理有多種方式,如回調(diào)、事件服務、通知服務等。事件服務和通知服務是CORBA用來處理異步消息的標準服務,他們主要負責消息的處理、派發(fā)、維護等工作。對一些簡單的異步處理過程,我們可以通過回調(diào)機制來實現(xiàn)。

                下面我們集中比較具有代表性的語言(C、Object Pascal)和架構(CORBA)來分析回調(diào)的實現(xiàn)方式、具體作用等。

                2 過程語言中的回調(diào)(C)


                2.1 函數(shù)指針
                回調(diào)在C語言中是通過函數(shù)指針來實現(xiàn)的,通過將回調(diào)函數(shù)的地址傳給被調(diào)函數(shù)從而實現(xiàn)回調(diào)。因此,要實現(xiàn)回調(diào),必須首先定義函數(shù)指針,請看下面的例子:

                void Func(char *s);// 函數(shù)原型
                void (*pFunc) (char *);//函數(shù)指針

                可以看出,函數(shù)的定義和函數(shù)指針的定義非常類似。

                一般的化,為了簡化函數(shù)指針類型的變量定義,提高程序的可讀性,我們需要把函數(shù)指針類型自定義一下。

                typedef void(*pcb)(char *);

                回調(diào)函數(shù)可以象普通函數(shù)一樣被程序調(diào)用,但是只有它被當作參數(shù)傳遞給被調(diào)函數(shù)時才能稱作回調(diào)函數(shù)。

                被調(diào)函數(shù)的例子:

                void GetCallBack(pcb callback)
                {
                /*do something*/
                }
                用戶在調(diào)用上面的函數(shù)時,需要自己實現(xiàn)一個pcb類型的回調(diào)函數(shù):
                void fCallback(char *s)
                {
                /* do something */
                }
                然后,就可以直接把fCallback當作一個變量傳遞給GetCallBack,
                GetCallBack(fCallback);

                如果賦了不同的值給該參數(shù),那么調(diào)用者將調(diào)用不同地址的函數(shù)。賦值可以發(fā)生在運行時,這樣使你能實現(xiàn)動態(tài)綁定。

                2.2 參數(shù)傳遞規(guī)則
                到目前為止,我們只討論了函數(shù)指針及回調(diào)而沒有去注意ANSI C/C++的編譯器規(guī)范。許多編譯器有幾種調(diào)用規(guī)范。如在Visual C++中,可以在函數(shù)類型前加_cdecl,_stdcall或者_pascal來表示其調(diào)用規(guī)范(默認為_cdecl)。C++ Builder也支持_fastcall調(diào)用規(guī)范。調(diào)用規(guī)范影響編譯器產(chǎn)生的給定函數(shù)名,參數(shù)傳遞的順序(從右到左或從左到右),堆棧清理責任(調(diào)用者或者被調(diào)用者)以及參數(shù)傳遞機制(堆棧,CPU寄存器等)。

                將調(diào)用規(guī)范看成是函數(shù)類型的一部分是很重要的;不能用不兼容的調(diào)用規(guī)范將地址賦值給函數(shù)指針。例如:

                // 被調(diào)用函數(shù)是以int為參數(shù),以int為返回值
                __stdcall int callee(int);

                // 調(diào)用函數(shù)以函數(shù)指針為參數(shù)
                void caller( __cdecl int(*ptr)(int));

                // 在p中企圖存儲被調(diào)用函數(shù)地址的非法操作
                __cdecl int(*p)(int) = callee; // 出錯

                指針p和callee()的類型不兼容,因為它們有不同的調(diào)用規(guī)范。因此不能將被調(diào)用者的地址賦值給指針p,盡管兩者有相同的返回值和參數(shù)列

                2.3 應用舉例
                C語言的標準庫函數(shù)中很多地方就采用了回調(diào)函數(shù)來讓用戶定制處理過程。如常用的快速排序函數(shù)、二分搜索函數(shù)等。

                快速排序函數(shù)原型:

                void qsort(void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
                二分搜索函數(shù)原型:
                void *bsearch(const void *key, const void *base, size_t nelem,
                size_t width, int (_USERENTRY *fcmp)(const void *, const void *));

                其中fcmp就是一個回調(diào)函數(shù)的變量。

                下面給出一個具體的例子:

                #include <stdio.h>
                #include <stdlib.h>

                int sort_function( const void *a, const void *b);
                int list[5] = { 54, 21, 11, 67, 22 };

                int main(void)
                {
             int x;

             qsort((void *)list, 5, sizeof(list[0]), sort_function);
             for (x = 0; x < 5; x++)
             printf("%i\n", list[x]);
             return 0;
             }

             int sort_function( const void *a, const void *b)
             {
             return *(int*)a-*(int*)b;
             }

             2.4 面向?qū)ο笳Z言中的回調(diào)(Delphi)

             Dephi與C++一樣,為了保持與過程語言Pascal的兼容性,它在引入面向?qū)ο髾C制的同時,保留了以前的結(jié)構化特性。因此,對回調(diào)的實現(xiàn),也有兩種截然不同的模式,一種是結(jié)構化的函數(shù)回調(diào)模式,一種是面向?qū)ο蟮慕涌谀J健?br>
            http://www.dream2fly.net/blog/?action=show&id=98

            精品久久久久久无码专区不卡 | 国内精品久久久久国产盗摄| 久久久久久综合一区中文字幕| 久久福利片| 亚洲中文字幕久久精品无码APP| 91久久精品国产成人久久| 久久久久久伊人高潮影院| 国产精品午夜久久| 亚洲国产二区三区久久| 色婷婷久久久SWAG精品| 久久五月精品中文字幕| 国产精品青草久久久久福利99| 日韩美女18网站久久精品| 97久久超碰国产精品旧版| 久久久噜噜噜久久中文福利| 人妻少妇久久中文字幕| 久久青青草原亚洲av无码app | 奇米影视7777久久精品| 久久国产美女免费观看精品| 国产69精品久久久久99尤物| 77777亚洲午夜久久多喷| 久久精品18| 国产A级毛片久久久精品毛片| 久久精品亚洲日本波多野结衣| 色婷婷噜噜久久国产精品12p| 久久99国产精品一区二区| 久久99国产精品久久99果冻传媒| 国产精品中文久久久久久久| 色诱久久久久综合网ywww| 欧美粉嫩小泬久久久久久久 | 久久亚洲高清观看| 亚洲AV无码久久精品色欲| AV色综合久久天堂AV色综合在| 久久天天躁夜夜躁狠狠躁2022| 日韩美女18网站久久精品| 久久国产免费直播| 久久夜色撩人精品国产小说| 久久丝袜精品中文字幕| 久久精品成人免费国产片小草| 久久亚洲精品无码播放| 久久综合伊人77777麻豆|