記一個界面刷新相關的Bug

今天遇到一個比較有意思的bug, 這裏簡單記錄下。

Bug的症狀是經過拖拉邊框把咱們客戶端主窗口拖小以後,再最大化,會發現窗口顯示有問題, 看起來像是刷新問題, 有些地方顯示的不對了。
這裏要說明的是我這裏的主窗口是很是複雜的窗口, 裏面集成了不少組件(cpmponent),有不少層的子窗口。 這個問題只有在特定條件下才會發生, 正常狀況下都是好的。

遇到這種問題,咱們怎麼處理? 

首先固然是觀察症狀, 到底是刷新問題, 仍是Layout出錯了。
咱們能夠經過Spy++查看窗口層次是否是正確, 窗口位置是否是對的。
查看結果是窗口的層次和Layout位置都沒有問題。

既然咱們這裏遇到的刷新問題,因此咱們要從WM_PAINT消息着手, 咱們經過Spy++查看相關窗口的WM_PAINT是否是正確。
很快咱們就會發現某個窗口正在不停地收到WM_PAINT消息, 極可能與咱們的bug有關。

一個窗口不停的收到WM_PAINT重畫, 無非大概有幾類緣由:
正常狀況是咱們正在作動畫, 多是經過定時器之類的東西讓窗口不停地InvalidateRect重畫某塊區域, 咱們的窗口明顯不屬於這種狀況。
討論異常狀況前先討論WM_PAINT消息,咱們知道WM_PAINT消息裏必定要調用BeginPaint和EndPaint, 前者告訴系統繪畫開始,系統會把當前窗口的無效區域變得有效, 後者結束某次繪畫。
異常狀況有時是WM_PAINT消息裏咱們的消息處理函數在某些條件下直接返回了,從而沒有調用BeginPaint告訴窗口無效區域已經有效, 這樣會由於由於窗口一直有無效區域存在,致使窗口一直收到WM_PAINT消息。
還有一種異常狀況狀況是咱們是在WM_PAINT消息裏調用BeginPaint後又調用了InvalidateRect, 這樣會致使窗口後面會再次收到WM_PAINT消息, 最後窗口陷入WM_PAINT的死循環。 

那麼咱們這裏的問題窗口屬於哪類? 用什麼方法能夠判斷出來?

注意到這裏關鍵的三個API:BeginPaint, EndPaint, InvalidateRect的第一個參數都是窗口句柄, 咱們能夠經過WinDbg的API斷點來跟蹤執行過程, Attach WinDbg到咱們的主窗口進程,好比咱們的窗口句柄是0x209A0, 咱們能夠這樣設置API斷點:
bp USER32!NtUserInvalidateRect ".if(dwo(@esp+0x4)==0x209A0) {kv;gc} .else {gc}"
bp USER32!NtUserBeginPaint ".if(dwo(@esp+0x4)==0x209A0) {kv;gc} .else {gc}"
bp USER32!NtUserEndPaint ".if(dwo(@esp+0x4)==0x209A0) {kv;gc} .else {gc}"
上面的條件斷點表示,當調用咱們的對應的API,而且第一個參數(窗口句柄)是咱們的目標窗口時,打印堆棧。

很快我定位出Bug發生的緣由了, 條件斷點顯示了API以下的調用次序:
BeginPaint->InvalidateRect->InvalidateRect->EndPaint
找到Bug的緣由後,而後把Bug assign給該模塊的負責人。 (看我夠意思吧,不只找到緣由,還把調用棧都提供了)

另外 ,後面還發現這個bug發生時窗口的某些行爲會不對, 測試發現緣由是全部窗口的定時器都不能正常工做了。
關於這個問題, 你能想到緣由嗎? 

若是想不到, 請把個人這篇博客《 從點擊Button到彈出一個MessageBox, 背後發生了什麼》看一遍。
若是看了還想不到, 重點看第4條。

最後, 簡單總結下:計算機的好處是它永遠不會欺騙你, 它只會循序漸進的執行, 因此不少看似奇怪(甚至看似難以想象的問題), 只要你理解了程序背後的機制原理,都是能夠找出根本緣由的。
相關文章
相關標籤/搜索