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

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

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

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

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

            1.       聲明;

            2.       定義;

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

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

             

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


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

                此調(diào)用機(jī)制并非完美。回調(diào)函數(shù)就是一例。函數(shù)之類本是為調(diào)用者準(zhǔn)備的美餐,其烹制者應(yīng)對(duì)食客了如指掌,但實(shí)情并非如此。例如,寫一個(gè)快速排序函數(shù)供他人調(diào)用,其中必包含比較大小。麻煩來了:此時(shí)并不知要比較的是何類數(shù)據(jù)--整數(shù)、浮點(diǎn)數(shù)、字符串?于是只好為每類數(shù)據(jù)制作一個(gè)不同的排序函數(shù)。更通行的辦法是在函數(shù)參數(shù)中列一個(gè)回調(diào)函數(shù)地址,并通知調(diào)用者:君需自己準(zhǔn)備一個(gè)比較函數(shù),其中包含兩個(gè)指針類參數(shù),函數(shù)要比較此二指針?biāo)笖?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é)構(gòu)亂了許多。Windows API 函數(shù)集中有不少回調(diào)函數(shù),盡管有詳盡說明,仍使初學(xué)者一頭霧水??峙逻@也是無奈之舉。

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

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

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

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

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

             

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

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

             


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

                對(duì)于一般的結(jié)構(gòu)化語(yǔ)言,可以通過回調(diào)函數(shù)來實(shí)現(xiàn)回調(diào)?;卣{(diào)函數(shù)也是一個(gè)函數(shù)或過程,不過它是一個(gè)由調(diào)用方自己實(shí)現(xiàn),供被調(diào)用方使用的特殊函數(shù)。

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

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

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

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

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


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

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

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

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

                typedef void(*pcb)(char *);

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

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

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

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

                2.2 參數(shù)傳遞規(guī)則
                到目前為止,我們只討論了函數(shù)指針及回調(diào)而沒有去注意ANSI C/C++的編譯器規(guī)范。許多編譯器有幾種調(diào)用規(guī)范。如在Visual C++中,可以在函數(shù)類型前加_cdecl,_stdcall或者_(dá)pascal來表示其調(diào)用規(guī)范(默認(rèn)為_cdecl)。C++ Builder也支持_fastcall調(diào)用規(guī)范。調(diào)用規(guī)范影響編譯器產(chǎn)生的給定函數(shù)名,參數(shù)傳遞的順序(從右到左或從左到右),堆棧清理責(zé)任(調(diào)用者或者被調(diào)用者)以及參數(shù)傳遞機(jī)制(堆棧,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中企圖存儲(chǔ)被調(diào)用函數(shù)地址的非法操作
                __cdecl int(*p)(int) = callee; // 出錯(cuò)

                指針p和callee()的類型不兼容,因?yàn)樗鼈冇胁煌恼{(diào)用規(guī)范。因此不能將被調(diào)用者的地址賦值給指針p,盡管兩者有相同的返回值和參數(shù)列

                2.3 應(yīng)用舉例
                C語(yǔ)言的標(biāo)準(zhǔn)庫(kù)函數(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就是一個(gè)回調(diào)函數(shù)的變量。

                下面給出一個(gè)具體的例子:

                #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(yǔ)言中的回調(diào)(Delphi)

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


            只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


            久久综合给合久久狠狠狠97色69| 久久se精品一区二区影院| 亚洲国产成人精品久久久国产成人一区二区三区综 | 国内精品伊人久久久久影院对白 | 中文字幕无码久久久| 久久99精品国产麻豆蜜芽| 狠狠人妻久久久久久综合| 久久综合狠狠综合久久激情 | 久久精品一区二区| 爱做久久久久久| 亚洲成av人片不卡无码久久| 久久久久99这里有精品10| 国产偷久久久精品专区 | 久久久精品久久久久久 | 国产成人精品综合久久久久| 亚洲人成网站999久久久综合| 中文字幕精品久久| 久久99精品久久久久久hb无码 | 久久免费视频观看| 久久久久国产精品嫩草影院 | 精品久久久久久无码中文字幕| 久久播电影网| 久久精品人妻中文系列| 日韩亚洲欧美久久久www综合网 | 欧美久久精品一级c片片| 久久99精品久久久久久不卡| 久久伊人五月丁香狠狠色| 成人妇女免费播放久久久| 深夜久久AAAAA级毛片免费看| 久久AV高潮AV无码AV| 伊人久久综在合线亚洲2019| 精品久久久久久无码不卡| 久久综合中文字幕| 久久综合亚洲色一区二区三区| 久久99精品国产99久久| 久久婷婷国产剧情内射白浆| 伊人久久大香线蕉精品| 九九久久自然熟的香蕉图片| 亚洲国产小视频精品久久久三级 | 色欲久久久天天天综合网精品 | 久久成人国产精品二三区|