天天看點

WinEyes的重新實作--windows系統及其消息機制

windows和x系統是相似的,然而它既不以程序為根本,又不以線程為根本,而是以視窗為根本的,由于它的過程的每一個環節都是在本機進行的,是以它必然需要在程序,線程以及視窗之間進行更進一步的細分,畢竟在沒有虛拟機硬體的支援下,一台機器的最小元素就是線程(在多處理情況下)。具體過程是,當有滑鼠鍵盤或者觸摸屏之類的東西有輸入事件時,windows系統會将這個事件排入到目前程序的目前線程的消息隊列中,注意是基于線程的,然後由目前程序應用程式通過GetMessge這個API依次取出一個消息,然後再次壓入windows系統,而後windows系統将這個消息分發到目前的視窗,注意這個過程中,将消息分派到目前視窗是通過線程來進行的,第一個過程是系統将事件排入目前線程,第二個過程是系統将一個消息排入目前線程的視窗,因為一個線程可能不僅僅包括一個視窗,windows系統的一切可操作元素都是通過視窗來組織的(witget),就連标題欄也是一個視窗。

     這就可以看出,windows的GUI和x系統的GUI的不同之處,由于x系統中,是通過socket這種程序間通信的方式來進行通信的,是以輸入裝置和顯示這種事天生就是接觸耦合的,況且,顯示圖形的機制是使用者空間完成,輸入裝置通過裝置檔案(/dev/input/mice...)被讀到使用者空間,然後x server程序通過x網絡協定分發這些事件到各個x client。x系統的程序間通信天生就不假設是同一台機器的,是以就不能用線程這種輕量級的機制來進一步裁剪機制--畢竟線程不能在不同機器間被認同--它們不能共享程序位址空間,是以x系統的最小單元就是程序。在windows系統中,由于事件和顯示都是同一台機器完成的,是以盡可以充分使用作業系統内部的機制,譬如線程來更加高效的完成,是以windows作業系統中就充分使用了線程消息隊列,然後線上程環境中将各個消息分派到目前視窗,這是因為這一點,GetMessage之後不能手工調用視窗過程函數,而要通過DispatchMessage函數來分發同一線程的消息到可能有多個的同一線程的不同視窗的之一。

     明白了windows的消息處理原理,很容易寫出一個windows版本的xeyes程式,然而在看既有的wineyes程式源代碼的時候,發現它也是通過timer實作的,這個令人很不爽,正如在linux中遇到的那樣!是以有必要改造之,既然我們的linux改造版的xeyes使用了和windows類似的事件循環機制,那麼我們的windows也可以和linux更加靠攏,那就是不調用DispatchMessage函數,而直接調用視窗處理函數,前提是消息确實是發送給我們這個視窗的--畢竟消息是從線程的消息隊列中取出的而消息的目的地卻是基于視窗的--一個線程不止一個視窗!整個思路是這樣的,我們一直都以為windows的處理機制和x window有着類似之處,隻不過windows由于x server和x client處于一個作業系統空間,是以它們可以充分使用作業系統的特性,比如線程,比如無消息時排程等特性。既然它們的本質是一樣的,那麼它們的實作也可以相通,既然x系統可以使用xlib來實作一個消息循環,那麼windows也是可以的,隻要我們排除掉它使用的作業系統特性即可,是以,我們加一個限制條件:

while(GetMessage(...)) {

    TranslateMessage(...);

    if (msg.hwnd == hWnd) {

        直接調用視窗函數

    } else {

        DispatchMessage(...);

    }

}

可見,隻要直接調用視窗函數就可以了,而沒有必要讓作業系統dispatch消息了。然而除了因為一個線程可能對應多個視窗導緻的消息不一定是給我們的這個原因外,還有什麼原因導緻必須調用dispatch呢?這和windows基于線程排程是有一定關系的,這與其說是一種完美的設計不如說是一種敗筆,之是以這麼設計為的就是作業系統更好的監控視窗函數的處理情況,如果直接調用視窗而不是調用dispatch,那麼作業系統完全不知道你的視窗函數什麼時候傳回的,如果你的視窗函數正在繪圖,此時将該線程排程出去是不合适的,除非它占用了太久的cpu或者有更要緊的事要做,是以windows作業系統最好能設法監控到視窗函數什麼時候傳回,以決定排程時機。這個機制無論如何都太複雜了,是以就簡單性原則來說就是敗筆,x系統就不需要這麼做,因為它基于task_struct排程,簡單,高效,不管task_struct到底是什麼,是以linux也根本不覺得将正在繪圖的task_struct排程出去有什麼不合适的,在windows中,GUI有特權,在linux中GUI隻是一個X程序,它沒有特權,一般!

     下面就是wineyes的無timer版本,也不知道為什麼,XXeyes怎麼都使用timer來做,搞不懂:

...

#pragma comment(linker, "/subsystem:/"console/" /entry:/"WinMainCRTStartup/"")  //便于調試,因為有console視窗

void setClippingRegion(HWND hWnd)

{

    ...//完全是wineyes的

void WinEyesUpdate(HWND hWnd, int ForceRedrawEyes)

void WinEyesPaint(HWND hWnd)

LRESULT CALLBACK PASCAL WinEyesWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

    ...//完全是wineyes的,但是去掉了WM_TIMER,因為這是無timer版的

BOOL WinEyesInit(HINSTANCE hInstance)

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

    while( GetMessage( &msg, NULL, (int)NULL, (int)NULL) ) { //和xlib類似的消息(事件)循環

        TranslateMessage(&msg);

        if (hWnd == msg.hwnd) { //如果消息是發給本視窗的,直接調用

            WinEyesWndProc(msg.hwnd, msg.message, msg.wParam, msg.lParam);

        }

        else //注意,這是在從線程消息隊列取消息,它可能不屬于我們這個視窗,如果這樣就調用dispatch,将之交由作業系統來處理,其實是作業系統負責将之路由到msg.hwnd

            DispatchMessage(&msg);

    ...

 本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1271164

繼續閱讀