• 沒有找到結果。

視窗程式建立流程 視窗程式建立流程 視窗程式建立流程 視窗程式建立流程

2.3 Win32 API

2.3.1 視窗程式建立流程 視窗程式建立流程 視窗程式建立流程 視窗程式建立流程

此小節要介紹和一個很重要的概念:Handle,和完整 Win32 程式建立流程。

●Handle 概念

視窗程式全都是模組化的程式--每個視窗元件,例如選單、捲軸、按鈕等,

都是一個獨立的個體,每個個體或元件都有自己的資料和函數,由於一個程式有 可能會有多個相同種類的元件(例如一個程式裡有很多按鈕),那麼在眾多相同的 元件中,要決定控制哪個元件,就需要設定 Handle,來指明控制哪個元件,例 如視窗程式的主視窗,就需要一個 Handler,來指明使用者現在正控制該程式的 視窗,而非其他,故 Handler 的概念非常重要。

●Win32 程式建立流程[5]

int PASCAL WinMain(……)

{ HWND hwnd; //宣告程式的 Handle

MSG msg; //宣告 MSG 結構來存放訊息 WNDCLASS wc;//宣告類別結構

InitWindow( …) ) //創建主窗口

while (GetMessage(&msg, NULL, 0, 0)) //訊息迴圈 { TranslateMessage(&msg); //解析 msg

DispatchMessage(&msg); //呼叫訊息處理函數 }

接著在 InitWindow(…)中,會先對類別結構 WNDCLASS 做設定,然後調用 RegisterClass()對該類別進行註冊。因為每個視窗程式都有一些基本的屬性,如

2.3.2 訊息迴圈 訊息迴圈 訊息迴圈 訊息迴圈

將這些訊息一個個的取出來,放進一個 MSG 結構中去。GetMessage()函數原型 如下:[6]

BOOL GetMessage(

LPMSG lpMsg, //指向一個 MSG 結構的指標,用來保存訊息 HWND hWnd, //指定哪個視窗的訊息將被獲取

UINT wMsgFilterMin, //指定獲取的主訊息值的最小值 UINT wMsgFilterMax //指定獲取的主訊息值的最大值 );

GetMessage()將獲取的訊息複製到一個 MSG 結構中。如果佇列中沒有任何 訊息,GetMessage()函數將一直空閒直到佇列中又有訊息時再返回。如果佇列中

該結構中的主訊息表明了訊息的類型,例如是鍵盤訊息還是滑鼠訊息等,副 參數是用來過濾 MSG 結構中主訊息值的,主訊息值在 wMsgFilterMin 和 wMsgFilterMax 之外的訊息將被過濾掉。如果這兩個參數為 0,則表示接收所有 訊息。當 GetMessage()函數在獲取到 WM_QUIT 訊息後,將返回 0 值,程式將 退出訊息迴圈。

●訊息處理函數

TranslateMessage()函數的作用是把虛擬鍵訊息轉換到字元訊息,以滿足鍵 盤輸入的需要。DispatchMessage()函數的工作是把當前的訊息,發送到該應用 程式對應的訊息處理函數去。

LRESULT CALLBACK WindowProc( //訊息處理函數 HWND hwnd, //接收訊息視窗的控制碼

Default:DefWindowProc(hWnd, uMsg, wParam, lParam); } }

Windows 的訊息處理函數有一個確定的樣式,即這種函數的參數個數和類型 以 及 其 返 回 值 的 類 型 都 有 明 確 的 規 定 。 訊 息 處 理 函 數 的 四 個 參 數 是 由 GetMessage()函數從訊息佇列中獲得 MSG 結構,然後分解後得到的。第二個參 數 uMsg 和 MSG 結構中的 message 值是一致的,代表了主訊息值。程式中用 switch 語句來將不同類型的訊息分配到不同的處理程式中去。

值 得 注 意 的 是 , 應 用 程 式 發 送 到 視 窗 的 訊 息 遠 遠 不 止 以 上 這 幾 條 , 像 WM_SIZE、WM_MINIMIZE、WM_CREATE、WM_MOVE 等這樣常常使用 的 訊 息 就 有 幾 十 條 。 為 了 減 輕 編 寫 程 式 的 負 擔 , Windows 的 API 提 供 了 DefWindowProc()函數來處理這些最常用的訊息,調用了這個函數後,這些訊息 將按照系統默認的方式得到處理。因此,在 switch_case 語句中,只須明確的處 理那些有必要進行特別回應的訊息,把其餘的訊息交給 DefWindowProc()函數來 處理,是一種明智的選擇,也是你必須做的一件事。

●結束訊息迴圈

當用戶按 Alt+F4 或單擊視窗右上角的退出按鈕,系統就向應用程式發送 一條 WM_DESTROY 的訊息。在處理此訊息時,調用了 PostQuitMessage()函 數,該函數會給視窗的訊息佇列中發送一條 WM_QUIT 的訊息。在訊息迴圈中,

GetMessage()函數一旦檢索到這條訊息,就會返回 FALSE,從而結束訊息迴圈,

隨後,程式也結束。

2.3.3 WM_PAINT

Windows 裡有各種功能的訊息,功能和格式各有不同,限於篇幅,這邊就不 一一贅述,但遠端遠桌面系統最重要的便是得知畫面變動,進而壓縮這些區域傳 送給 Client,因此此節要介紹和畫面更新最相關的訊息:“WM_PAINT“[7]。

●WM_PAINT 訊息

Windows 通過發送 WM_PAINT 訊息通知視窗訊息處理程式,視窗的部分顯 示區域需要繪製。在 Windows 中,只能在視窗的顯示區域繪製文字和圖形,而

4. 程式使用 InvalidateRect 或 InvalidateRgn 函式刻意產生 WM_PAINT。

在某些情況下,顯示區域的一部分被臨時覆蓋,Windows 試圖保存一個顯示 區域,並在以後恢復它,在以下情況下,Windows”可能”發送 WM_PAINT 訊息:

1. Windows 擦除覆蓋了部分視窗的對話方塊或訊息方塊。

程式應該組織成可以保留繪製顯示區域需要的所有資訊,並在 Windows 給 視窗訊息函數發 WM_PAINT 訊息時才進行繪製。如果程式在其他時間需要更新 其顯示區域,它可以強制 Windows 產生一個 WM_PAINT。

●Update Region

儘管視窗訊息處理程一旦接收 WM_PAINT 訊息之後,就準備更新整個顯示 區域,但它經常只需要更新一個較小的區域。顯然,當對話方塊覆蓋了部分顯示 區域時,情況即是如此。在擦除對話方塊之後,需要重畫的只是先前被對話方塊 遮住的矩形區域。這個區域稱為“Invalid Region“或更新區域。正是顯示區域 內無效區域的存在,才提示了 Windows 將一個 WM_PAINT 訊息放在應用程式 的訊息佇列中。只有在顯示區域的某一部分失效時,視窗才會接受 WM_PAINT 訊息。

Windows 內部為每個視窗保存一個“繪圖資訊結構“,這個結構包含了包圍 無效區域的最小矩形的座標以及其他資訊,這個矩形就叫做“無效矩形“,有時 也稱為“無效區域“。如果在視窗訊息處理函數處理 WM_PAINT 訊息之前顯示 區域中的另一個區域變為無效,則 Windows 計算出一個包圍兩個區域的新的無 效區域,並將這種變化後的資訊放在繪製資訊結構中。Windows 不會將多個 WM_PAINT 訊息都放在訊息佇列中。

視窗訊息處理程式可以通過呼叫 InvalidateRect 使顯示區域內的矩形無效。

如果訊息佇列中已經包含一個 WM_PAINT 訊息,Windows 將計算出新的無效 矩形。否則,它將一個新的 WM_PAINT 訊息放入訊息佇列中。在接收到 WM_PAINT 訊息時,視窗訊息處理函數可以取得無效矩形的座標。通過呼叫 GetUpdateRect,可以在任何時候取得這些座標。

在處理 WM_PAINT 訊息處理期間,視窗訊息處理函數呼叫 BeginPaint 之 後,整個顯示區域即變為有效。程式也可以通過呼叫 ValidateRect 函式使顯示區 域內的任意矩形區域變為有效。如果這呼叫具有令整個無效區域變為有效,則目 前佇列中的任何 WM_PAINT 訊息都將被刪除。

●WM_PAINT 訊息處理 case WM_PAINT:

hdc = BeginPaint(hwnd,&ps);

使用 GDI 函數

EndPaint(hwnd,&ps);

Return ;

在處理 WM_PAINT 訊息,必須成對地呼叫 BeginPaint 和 EndPaint。

在呼叫完 BeginPaint 後,就開始呼叫 GDI 函數做一些畫面的處理。如果視窗訊 息處理程式不處理 WM_PAINT 訊息,則它必須將 WM_PAINT 訊息傳遞給 Windows 中 DefWindowProc(內定視窗訊息處理函數)。

Windows 將一個 WM_PAINT 訊息放到訊息佇列中,是因為顯示區域的一部 分無效。如果不呼叫 BeginPaint 和 EndPaint(或者 ValidateRect),則 Widnows 不會使該區域變為有效。相反,Windows 將發送另一個 WM_PAINT 訊息,且 一直發送下去。

2.3.4 Hook

因為遠端桌面程式最在意的便是畫面變動的區塊,因此如何取得這些區塊,

就是這類程式相當重要的一部分。VNC 採用的方法是,監視任何和畫面相關的訊 息,並將範圍記錄下來,再進一步處理,這些在後面章節會詳細探討,現階段我 們要先研究 VNC 是如何取得想要的訊息---Hook[8]。

Hook 實際上是一個處理訊息的程式,屬於 Windows 的 API,通過使用者的 啟動後,它可以針對某些特定動作所產生的訊息做監視和處理,比如:OS 發送 WM_PAINT 的訊息給某個應用程式、使用者敲擊鍵盤、移動滑鼠,這些就被分 類為三種動作,因為 Windows 溝通都是透過訊息,因此每當特定的動作產生時,

都會伴隨著訊息,在訊息還沒送達目的應用程式之前,Hook 就會先捕獲該訊息,

亦即 Hook 前處理函數先得到控制權,此時對應 idHook 的前處理函數即可對該 訊息加工,也可以不作處理而繼續傳遞該訊息,還可以強制結束訊息的傳遞。前 處理函數會如何運作的部分我們在 3.4.1 節中會詳細再說明。

圖 2-2.Hook 示意圖

要實現 win32 的系統 Hook,必須調用 SDK 中的 API 函數 SetWindows- HookEx 來安裝這個 Hook 函數,這個函數的原型是:

HHOOK SetWindowsHookEx(

int idHook, //要安裝的 Hook 類型 (參考下面的 IdHook)

HOOKPROC lpfn, //Hook 前處理函數的指標,即攔截到指定系統訊息後 的前處理函數名稱,須定義在 DLL 中

HINSTANCE hMod, //應用程式實例的控制碼

DWORD dwThreadId; //要安裝 Hook 的 threadID ,指定被監視的 thread,如 果明確指定了某個 thread 的 ID 就只監視該 thread,此 時的 Hook 即為 thread hook;如果該參數被設置為 0,

則表示此 Hook 為監視系統所有 thread 的全局 Hook。

);

得到控制權的 Hook 前處理函數在完成對訊息的處理後,如果想要該訊息繼 續傳遞,那麼它必須調用另外一個 SDK 中的 API 函數 CallNextHookEx 將訊息 傳遞下去。Hook 預處理函數也可以通過直接返回 true 來丟棄該訊息,並阻止該 訊息的傳遞。每種類型的 Hook 是由系統來維護,最後安裝的 Hook 放在鏈的開 始,而最早安裝的 Hook 放在最後,也就是後加入的先獲得控制權。Hook 在使 用完之後需要用 UnHookWindowsHookEx()卸載,否則會造成麻煩。

Windows 總共提供了對應 13 種動作的 Hook,這裡僅列出一般較常用的 9 種,旁邊是要觸發 Hook 對應動作的說明。

●常用 idHook 參數整理如下:

WH_CALLWNDPROC //視窗 hook,當系統向目標視窗發送訊息時將觸發 WH_CALLWNDPROCRET //視窗 hook,當視窗處理完訊息後將觸發

WH_CBT //當 Windows 啟動、產生、釋放(關閉)、最小

VNC Server 在程式中啟用了三個 Hook,分別是上面 WH_CALLWNDPROC、

WH_GETMESSAGE、WH_SYSMSGFILTER,並且在 SetWindowsHookEx()

第三章

Server 整個完整架構先進行初步介紹,對整個邏輯有點概念;3.2 節中說明 Server 是如何不斷接收不同 Client 的連線要求;3.3 節討論從 Server 和 Client 一開始連 入。由以上兩種方式取得最後可能變動範圍後,在最新資料的 mainbuffer 和前 一狀態的 backbuffer 中,詳細比對這些可能範圍,取得真正有變動的區域資料並 壓縮,再進一步傳送給 Client。

3.1 VNC Server 運作流程和架構 運作流程和架構 運作流程和架構 運作流程和架構

此章在先以流程圖大概描述 VNC Server 整個程式的運作流程,接著再以示

此章在先以流程圖大概描述 VNC Server 整個程式的運作流程,接著再以示