其實,我的C++入門就是從GDI開始的,想在CE上面寫應用程序,若兼程序界面太難看那就必須用回GDI了。GDI是一種古老而又非常麻煩的技術,在C#年代還好點,但VC++下,玩GDI記得最最重要的一點是,一定要注意GDI資源的回收,否則你的程序會沒跑幾下就彈出錯誤窗口,原因大概都是內存泄漏。所以凡是遇上CPen,CBrush,CBitmap,GetDC()...等等,請打醒十二分精神。
因爲以前吃了太多的虧,原則上我對GDI資源的回收還是挺有自信的,但這世界上總是存在着許多新的狀況跟不同的問題的,昨晚認真研究了自己一段有BUG代碼,在此作一番記錄。
凡在窗體上繪圖,必要用到雙緩存的技術,而這又離不開兩個函數:CreateCompatibleDC與CreateCompatibleBitmap,大概的意思,創建一個跟顯示屏幕格式一致的內存段,在此內存段裏畫好圖之後再拷貝到屏幕裏(這是我非標準的理解方式),具體的API用法網上有很多很詳細的教程,而正常的創建與資源回收的代碼如下:
- CDC * pDC = this->GetRealDC();//真實的窗體DC,一般在CWnd下使用GetDC()獲得
- CDC MemDC;
- CBitmap bmp;
- CBitmap * pBmp = NULL;
- CRect rc = this->GetRect();//窗體的尺寸,一般在CWnd類使用GetClientRect()獲得
- BOOL bRes = FALSE ;
- bRes = MemDC.CreateCompatibleDC(pDC);
- ASSERT(bRes);
- bRes = bmp.CreateCompatibleBitmap(pDC , rc.Width() , rc.Height());
- ASSERT(bRes);
- pBmp = MemDC.SelectObject(&bmp);
- ASSERT(pBmp);
- //Do something
- //...
- //一般兼容DC的回收原則是先創建後刪除
- MemDC.SelectObject(pBmp);
- bRes = pBmp->DeleteObject();
- pBmp = NULL;
- ASSERT(bRes);
- bRes = bmp.DeleteObject();
- ASSERT(bRes);
- bRes = MemDC.DeleteDC();
- ASSERT(bRes);
以上經自己實踐檢驗過的代碼,ASSERT()的部分都能通過,表示就是真的可以用,若你喜歡的話完全可以寫個while函數來測試一下,若資源沒回收的話,程序不到跑100次就已經掛掉了。但如果我在同一個pDC下創建兩個MemDC時,回收就會有問題了,代碼如下:
- CDC * pDC = this->GetRealDC();//真實的窗體DC,一般在CWnd下使用GetDC()獲得
- CDC MemDC;
- CBitmap bmp;
- CBitmap * pBmp = NULL;
- CRect rc = this->GetRect();//窗體的尺寸,一般在CWnd類使用GetClientRect()獲得
- CDC MemDC2;
- CBitmap bmp2;
- CBitmap * pBmp2 = NULL;
- BOOL bRes = FALSE ;
- bRes = MemDC.CreateCompatibleDC(pDC);
- ASSERT(bRes);
- bRes = bmp.CreateCompatibleBitmap(pDC , rc.Width() , rc.Height());
- ASSERT(bRes);
- pBmp = MemDC.SelectObject(&bmp);
- ASSERT(pBmp);
- bRes = MemDC2.CreateCompatibleDC(pDC);
- ASSERT(bRes);
- bRes = bmp2.CreateCompatibleBitmap(pDC , rc.Width() , rc.Height());
- ASSERT(bRes);
- pBmp2 = MemDC2.SelectObject(&bmp2);
- ASSERT(pBmp);
- //Do something
- //...
- MemDC.SelectObject(pBmp);
- bRes = pBmp->DeleteObject();
- pBmp = NULL;
- ASSERT(bRes);//通過
- bRes = bmp.DeleteObject();
- ASSERT(bRes);//通過
- bRes = MemDC.DeleteDC();
- ASSERT(bRes);//通過
- MemDC2.SelectObject(pBmp2);
- bRes = pBmp2->DeleteObject();
- pBmp2 = NULL;
- ASSERT(bRes);//失敗
- bRes = bmp2.DeleteObject();
- ASSERT(bRes);//失敗
- bRes = MemDC2.DeleteDC();
- ASSERT(bRes);//通過
經過調試後的結論是,pBmp與pBmp2是指向的是同一個東西,MemDC.SelectObject(pBmp)這一句話是斷開bmp與MemDC的關聯,若不事先斷開的話bmp.DeleteObject()就會失敗,所以一旦先執行了bRes = pBmp->DeleteObject()這一句,那MemDC.SelectObject(pBmp2)這一句就存在問題了(事實上pBmp2已經被刪掉了),導致後面全線崩潰。於是,如果是雙兼容DC的話,其正常的回收代碼應該如下所示。總之不管三七二十一,先斷開兼容DC與兼容位圖的關聯後,再作相關的資源回收。
- MemDC.SelectObject(pBmp);
- MemDC2.SelectObject(pBmp2);
- bRes = pBmp->DeleteObject();
- ASSERT(bRes);
- if (pBmp2 != pBmp)
- {
- bRes = pBmp2->DeleteObject();
- ASSERT(bRes);
- }
- pBmp = NULL;
- pBmp2 = NULL;
- bRes = bmp.DeleteObject();
- ASSERT(bRes);//通過
- bRes = MemDC.DeleteDC();
- ASSERT(bRes);//通過
- bRes = bmp2.DeleteObject();
- ASSERT(bRes);//通過
- bRes = MemDC2.DeleteDC();
- ASSERT(bRes);//通過