在微軟 .NET 框架中可以定義托管類事件并用委托和 += 操作符處理這些事件。這種機(jī)制似乎很有用,那么在本機(jī) C++ 中有沒有辦法做同樣的事情?
確實(shí)如此!Visual C++ .NET 具備所謂統(tǒng)一事件模型(Unified Event Model),它可以像托管類一樣實(shí)現(xiàn)本機(jī)事件(用 __event 關(guān)鍵字),但是由于本機(jī)事件存在一些不明顯的技術(shù)問題,而微軟的老大不打算解決這些問題,所以他們要我正式奉勸你不要使用它們。那么這是不是就是說 C++ 程序員與事件無緣了呢?當(dāng)然不是!可以通過別的方法實(shí)現(xiàn)。本文我將向你展示如何輕松實(shí)現(xiàn)自己漂亮的事件系統(tǒng)。
但是在動(dòng)手之前,讓我先大體上介紹一下事件和事件編程。它是個(gè)重要的主題,當(dāng)今對(duì)事件沒有堅(jiān)實(shí)的理解,你是無法編寫程序的——什么是事件以及什么時(shí)候使用事件。
成功的編程完全在于對(duì)復(fù)雜性的掌控。很久以前,函數(shù)被稱為“子程序”(我知道,我這樣說證明我已經(jīng)老了!)管理復(fù)雜性的主要方式之一是自頂向下的編程模式。高層實(shí)現(xiàn)類似“宇宙模型”,然后將它劃分為更小的任務(wù)如:“銀河系模型”以及“太陽系模型”等等,直到任務(wù)被劃分為可以用單個(gè)函數(shù)實(shí)現(xiàn)為止。目前自頂向下的編程模型仍被用于過程化的任務(wù)實(shí)現(xiàn)當(dāng)中,但它不適用于發(fā)生順序不確定的實(shí)時(shí)事件響應(yīng)系統(tǒng)。經(jīng)典的例子便是 GUI,程序必須響應(yīng)用戶的某些行為,比如按鍵或是鼠標(biāo)移動(dòng)。實(shí)際上,事件編程很大程度上源于圖形用戶界面的出現(xiàn)。
在自頂向下的模型中,在頂部的高級(jí)部分對(duì)低級(jí)的實(shí)現(xiàn)各種不同任務(wù)的函數(shù)——如 DoThis,DoThat 進(jìn)行食物鏈?zhǔn)降恼{(diào)用。但不久以后,低層部分需要回調(diào)(talk back),在 Windows 中,可以調(diào)用 Rectangle 或 Ellipse 繪制一個(gè)矩形或橢圓,但最終 Windows 需要調(diào)用你的應(yīng)用程序來畫窗口。但應(yīng)用程序都還不存在,它仍然處于被調(diào)用度狀態(tài)!那么 Windows 如何知道要調(diào)用哪個(gè)函數(shù)呢?這就是事件用處之所在。

Figure 1 自頂向下和自底向上
在每個(gè) Windows 程序的核心——不論是直接用 C 語言編寫的還是使用 MFC 或 .NET 框架類編寫——都是一個(gè)處理消息的窗口過程,這些消息如:WM_PAINT, WM_SETFOCUS 和 WM_ACTIVATE。你(MFC 或 .NET)實(shí)現(xiàn)窗口過程并將它傳遞給 Windows。到了該畫窗口,改變輸入焦點(diǎn)以及激活窗口的時(shí)候,Windows 用相應(yīng)的消息代碼調(diào)用你的過程。這個(gè)消息就是事件。窗口過程就是事件處理器。如果過程化編程是自頂向下的,事件編程是自底向上。在典型的軟件系統(tǒng)中,函數(shù)的調(diào)用流是從較高級(jí)部分到低級(jí)部分進(jìn)行的;而事件是以相反的方向過濾的,如Figure 1 所示。當(dāng)然,在現(xiàn)實(shí)的開發(fā)中層次關(guān)系并不總是這么清晰。許多軟件系統(tǒng)看起來更像 Figure 2 所示的情況:

Figure 2 混合模型
那么到底什么叫事件?其實(shí),事件就是回調(diào)。而不是在編譯時(shí)就已知名字的函數(shù)調(diào)用,組件調(diào)用在運(yùn)行時(shí)調(diào)用你提供的函數(shù)。在 Windows 中,它是一個(gè)窗口過程。在 .NET 框架中,它叫做委托。不管術(shù)語怎么叫,事件提供了一種軟件組件調(diào)用函數(shù)的方式,這種調(diào)用方式直到運(yùn)行時(shí)才知道要調(diào)用什么函數(shù)?;卣{(diào)被稱為事件處理器。發(fā)生或觸發(fā)一個(gè)事件意味調(diào)用這個(gè)事件處理器。為此,事件接收部分首先得給事件源提供一個(gè)事件處理器的指針,這個(gè)過程叫注冊(cè)。
通常在以下幾種場(chǎng)合下我們要使用事件:
通知客戶機(jī)實(shí)際的事件:用戶按下某個(gè)按鍵;午夜時(shí)鐘敲響;風(fēng)扇停止工作造成 CPU 燒毀;
當(dāng)拷貝文件或搜索巨型數(shù)據(jù)庫時(shí),報(bào)告耗時(shí)操作的過程,組件可以周期性地觸發(fā)某個(gè)事件以報(bào)告已拷貝了多少文件或已搜索了多少記錄;如果你使用 IWebBrowser2 在自己的應(yīng)用程序中宿主 IE,報(bào)告所發(fā)生的重要的或引起注意的事件,瀏覽器會(huì)在導(dǎo)航到某個(gè)新頁面之前或之后通知你,或者在創(chuàng)建一個(gè)新窗口時(shí)通知你。
調(diào)用應(yīng)用程序提供的算法:C 運(yùn)行時(shí)庫函數(shù) qsort 排序?qū)ο髷?shù)組,但你必須提供比較函數(shù)。借助許多 STL 容器也能實(shí)現(xiàn)同樣的訣竅.大多數(shù)程序員不會(huì)調(diào)用 qsort 回調(diào)某個(gè)事件,但你沒有理由不考慮那種方式。它是“時(shí)間比較”事件。
一些讀者問:異常和事件之間有什么差別?主要差別是:異常表示不應(yīng)該發(fā)生的意外情況。例如,你的程序運(yùn)行耗盡內(nèi)存,或者遇到被零除。這些都是你并不希望發(fā)生的異常情況,并且一旦出現(xiàn)這些情況,你的程序必須要做出相應(yīng)的處理。另一方面,事件則是每天常規(guī)操作的部分并且完全是預(yù)期的。用戶移動(dòng)鼠標(biāo)或按下某個(gè)鍵。瀏覽器導(dǎo)航到一個(gè)新頁面。從控制流的角度看,事件是一次函數(shù)調(diào)用,而異常則是堆棧的突然跳躍,用展開的語義銷毀丟失的對(duì)象。
有關(guān)事件常見的概念誤解是認(rèn)為它們是異步的。雖然事件常常被用于處理用戶輸入和其它異步發(fā)生的行為 ,但事件本身是以同步方式發(fā)生的。觸發(fā)一個(gè)事件與調(diào)用該事件處理器是同一件事情。用偽碼表示就像如下的代碼段:
// raise Foo event
for (/* each registered object */) {
obj->FooHandler(/* args */);
}
控制立即傳到事件處理器,并且不會(huì)返回,除非處理完成。某些系統(tǒng)提供某種以異步觸發(fā)事件的方式,例如,在 Windows 中,你可以用 PostMessage 代替 SendMessage。控制會(huì)從 PostMessage 立即返回,該消息是后來才處理的。但是 .NET 框架中的事件以及我在這里討論的事件是在觸發(fā)時(shí)被立即處理的。當(dāng)然,你總是可以觸發(fā)來自運(yùn)行在單獨(dú)的線程中的消息代碼事件,或者使用異步委托調(diào)用在線程池中執(zhí)行每個(gè)事件處理器,在這種情況下,相對(duì)于主線程來說,事件是異步發(fā)生的。Windows 處理事件的方式完全是通過窗口過程以及一成不變的 WPARAM/LPARAM 參數(shù),按照現(xiàn)代編程標(biāo)準(zhǔn)來說,簡(jiǎn)陋而粗糙。即便是在今天,每個(gè) Windows 程序仍然在使用這種機(jī)制。有些程序員為了傳遞事件,甚至創(chuàng)建不可見窗口。窗口過程并不是真正意義上的事件機(jī)制,因?yàn)樵?Winodows 中每個(gè)窗口只允許有一個(gè)窗口過程,雖然也可以鏈接多個(gè)過程,比如每個(gè)過程都調(diào)用其前面的過程,也就是眾所周知的子類化過程。在真正的事件系統(tǒng)中,相同的事件可以不分等級(jí)地注冊(cè)多個(gè)接收者。
新聞熱點(diǎn)
疑難解答