CreateCompatibleBitmap,關於兼容DC的創建與資源回收

其實,我的C++入門就是從GDI開始的,想在CE上面寫應用程序,若兼程序界面太難看那就必須用回GDI了。GDI是一種古老而又非常麻煩的技術,在C#年代還好點,但VC++下,玩GDI記得最最重要的一點是,一定要注意GDI資源的回收,否則你的程序會沒跑幾下就彈出錯誤窗口,原因大概都是內存泄漏。所以凡是遇上CPen,CBrush,CBitmap,GetDC()...等等,請打醒十二分精神。

因爲以前吃了太多的虧,原則上我對GDI資源的回收還是挺有自信的,但這世界上總是存在着許多新的狀況跟不同的問題的,昨晚認真研究了自己一段有BUG代碼,在此作一番記錄。

凡在窗體上繪圖,必要用到雙緩存的技術,而這又離不開兩個函數:CreateCompatibleDC與CreateCompatibleBitmap,大概的意思,創建一個跟顯示屏幕格式一致的內存段,在此內存段裏畫好圖之後再拷貝到屏幕裏(這是我非標準的理解方式),具體的API用法網上有很多很詳細的教程,而正常的創建與資源回收的代碼如下:

  1. CDC * pDC = this->GetRealDC();//真實的窗體DC,一般在CWnd下使用GetDC()獲得 
  2. CDC MemDC; 
  3. CBitmap bmp; 
  4. CBitmap * pBmp = NULL; 
  5. CRect rc = this->GetRect();//窗體的尺寸,一般在CWnd類使用GetClientRect()獲得 
  6.  
  7. BOOL bRes = FALSE ; 
  8. bRes = MemDC.CreateCompatibleDC(pDC); 
  9. ASSERT(bRes); 
  10. bRes = bmp.CreateCompatibleBitmap(pDC , rc.Width() , rc.Height()); 
  11. ASSERT(bRes); 
  12. pBmp = MemDC.SelectObject(&bmp); 
  13. ASSERT(pBmp); 
  14.  
  15. //Do something 
  16. //... 
  17.  
  18. //一般兼容DC的回收原則是先創建後刪除 
  19. MemDC.SelectObject(pBmp); 
  20. bRes = pBmp->DeleteObject();     
  21. pBmp = NULL; 
  22. ASSERT(bRes); 
  23. bRes = bmp.DeleteObject(); 
  24. ASSERT(bRes); 
  25. bRes = MemDC.DeleteDC(); 
  26. ASSERT(bRes); 

以上經自己實踐檢驗過的代碼,ASSERT()的部分都能通過,表示就是真的可以用,若你喜歡的話完全可以寫個while函數來測試一下,若資源沒回收的話,程序不到跑100次就已經掛掉了。但如果我在同一個pDC下創建兩個MemDC時,回收就會有問題了,代碼如下:

  1. CDC * pDC = this->GetRealDC();//真實的窗體DC,一般在CWnd下使用GetDC()獲得 
  2. CDC MemDC; 
  3. CBitmap bmp; 
  4. CBitmap * pBmp = NULL; 
  5. CRect rc = this->GetRect();//窗體的尺寸,一般在CWnd類使用GetClientRect()獲得 
  6.  
  7. CDC MemDC2; 
  8. CBitmap bmp2; 
  9. CBitmap * pBmp2 = NULL; 
  10.  
  11. BOOL bRes = FALSE ; 
  12. bRes = MemDC.CreateCompatibleDC(pDC); 
  13. ASSERT(bRes); 
  14. bRes = bmp.CreateCompatibleBitmap(pDC , rc.Width() , rc.Height()); 
  15. ASSERT(bRes); 
  16. pBmp = MemDC.SelectObject(&bmp); 
  17. ASSERT(pBmp); 
  18.  
  19. bRes = MemDC2.CreateCompatibleDC(pDC); 
  20. ASSERT(bRes); 
  21. bRes = bmp2.CreateCompatibleBitmap(pDC , rc.Width() , rc.Height()); 
  22. ASSERT(bRes); 
  23. pBmp2 = MemDC2.SelectObject(&bmp2); 
  24. ASSERT(pBmp); 
  25.  
  26. //Do something 
  27. //... 
  28.  
  29. MemDC.SelectObject(pBmp); 
  30. bRes = pBmp->DeleteObject();     
  31. pBmp = NULL; 
  32. ASSERT(bRes);//通過 
  33. bRes = bmp.DeleteObject(); 
  34. ASSERT(bRes);//通過 
  35. bRes = MemDC.DeleteDC(); 
  36. ASSERT(bRes);//通過 
  37.  
  38. MemDC2.SelectObject(pBmp2); 
  39. bRes = pBmp2->DeleteObject();    
  40. pBmp2 = NULL; 
  41. ASSERT(bRes);//失敗 
  42. bRes = bmp2.DeleteObject(); 
  43. ASSERT(bRes);//失敗 
  44. bRes = MemDC2.DeleteDC(); 
  45. ASSERT(bRes);//通過 

經過調試後的結論是,pBmp與pBmp2是指向的是同一個東西,MemDC.SelectObject(pBmp)這一句話是斷開bmp與MemDC的關聯,若不事先斷開的話bmp.DeleteObject()就會失敗,所以一旦先執行了bRes = pBmp->DeleteObject()這一句,那MemDC.SelectObject(pBmp2)這一句就存在問題了(事實上pBmp2已經被刪掉了),導致後面全線崩潰。於是,如果是雙兼容DC的話,其正常的回收代碼應該如下所示。總之不管三七二十一,先斷開兼容DC與兼容位圖的關聯後,再作相關的資源回收。

  1. MemDC.SelectObject(pBmp); 
  2. MemDC2.SelectObject(pBmp2); 
  3. bRes = pBmp->DeleteObject(); 
  4. ASSERT(bRes); 
  5.  
  6. if (pBmp2 != pBmp) 
  7.     bRes = pBmp2->DeleteObject(); 
  8.     ASSERT(bRes); 
  9.  
  10. pBmp = NULL; 
  11. pBmp2 = NULL; 
  12.  
  13. bRes = bmp.DeleteObject(); 
  14. ASSERT(bRes);//通過 
  15. bRes = MemDC.DeleteDC(); 
  16. ASSERT(bRes);//通過 
  17.  
  18. bRes = bmp2.DeleteObject(); 
  19. ASSERT(bRes);//通過 
  20. bRes = MemDC2.DeleteDC(); 
  21. ASSERT(bRes);//通過 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章