關於應用程式出現視窗不完整,GDI對象猛增,GDI資源泄漏的問題的探討

來源:互聯網
上載者:User

http://hi.baidu.com/qi_xian/blog/item/08011716e096751e962b4345.html

本文轉載於3SDN: http://www.3sdn.net

有時候,一個應用程式運行到一定的時間,會出現視窗不完整(花屏),出現“必需的資源無法得到”的報錯,這是個令人煩惱的問題。此時,你如果開啟資源管理員,在“查看”中“選擇列”,添加“GDI對象”,可以很清晰得看到,隨著程式的運行,GDI對象,快速地增加,當數量達到9999時(為什麼是這個數,下文會提到)時,程式視窗介面就會出現不完整現象,此時,你若拖動程式裡的捲軸之類的,將會出現嚴重的花屏,甚至還會彈出一個不完整的警告框,警告:“必需的資源無法得到”。

這是典型的GDI資源泄漏的問題。

之所以會出現這樣的問題,主要是你在程式中建立了GDI對象,之後並沒有釋放或消毀等等。這種問題一般出現在OnPaint(),Draw()還有一些涉及到繪圖的函數中。下面舉例說明:

1、一個新的GDI對象選擇到了DC,但使用完後沒有恢複DC中的原始GDI對象。
CGDIObject *pOldDC=pDC->SelectObject(&yourobject);
//......  
pDC->SelectObject(pOldDC); //在繪圖結束時,這句不能少

2、通過GetDC()等獲得的上下文CDC,使用後,必須Realease。

3、建立 BITMAP 對象,最後要DeleteObject()。

4、這是我遇到的問題:在做檔案搜尋時,用到CListCtrl顯示搜尋結果,我想在檔案名稱前顯示檔案表徵圖,因此用函數:

::SHGetFileInfo (pathname, FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(SHFILEINFO),SHGFI_USEFILEATTRIBUTES | SHGFI_DISPLAYNAME | SHGFI_TYPENAME|SHGFI_ICON|SHGFI_SMALLICON))

得到檔案的表徵圖,但後來我並沒有用到,因此就放那不管了,由此“鑄成大錯”,後果不堪設想,如上文所描述的那樣。解決方案是去掉紅色部分就可以了。這時,運行程式,GDI對象的數量一般穩定在32-35之間,不會出現一瞬間就飆升到9999的可怕景象了。

下面說說GDI對象問題。

為解決我遇到的問題,我上網尋覓許久,最後看到henan_lujun(網名,引自:http://www.programfan.com/club/showpost.asp?id=7995)的分析,明白些許,下面轉載片段:

     GDI對象,實際上是Windows系統維護的一些資料結構。微軟基於穩定性和健壯性考慮,將所有GDI對象的管理權都交給Windows系統的對象管理器管理,使用者只能通過系統返回的“控制代碼”來操作這些對象。
     在Windows 2000中,控制代碼實際上是一個DWORD類型的值。該DWORD值是一個32位元位的資料,它又分為兩個部分:Table Index及Uniqueness Identifier,他們各佔16位,因此,在理論上來說,Windows中的每個進程,所能訪問的GDI對象的最大值是64K。然而,在Windows 2000中,客戶控制代碼的最大數目被硬設定為16384 (16K);
     然而,在Windows 2000中,既便客戶控制代碼的最大數目被硬設定為16384,那麼為什麼在實際中GDI對象增加到9999後,程式介面就開始混亂了呢?原來,在Windows2000中,每進程的GDI對象的最大值又被預設為10000——據微軟資料顯示,之所以設定為10000,是為了阻止“Bad”程式分配過多的資源,因此,當GDI對象達到9999之後,程式就不能再建立新的GDI資源,這樣,每次都使用建立資源來繪製介面的程式就產生混亂了。
      不過,在Windows 2000及以後的作業系統中,每個進程可以建立的GDI對象的最大值,是可以通過修改註冊表來重新設定的,在Windows 2000中,該登錄機碼所為:HKEY_LOCAL_MACHINE/ SOFTWARE/Microsoft/Windows NT/CurrentVersion/Windows下的"GDIProcessHandleQuota"。設定完新的值後,重啟電腦後,系統中每進程可以使用的GDI對象數就會變成你新設定的數。
然而,上述說法並不完全正確!
     據SYBASE的一份資料顯示(http://www.sybase.com/detail?id=1019174),在Windwos2000中,只可以對”GDIProcessHandleQuota”值進行微調,如果設定的值超過15000,系統就變得不穩定。事實上,我在Windows 2000中進行了測試,當GetGuiResources函數的傳回值為12288(12K)時,就已經不能建立新的GDI對象了,也就是說,在Windows 2000中當一個進程總的GDI對象數達到12288以後,就不能再建立新的GDI對象了。
     那麼,在Windows 2000中,進程所能建立的GDI對象數,是每個進程獨立的,還是要受限於Windows作業系統呢?為此,我寫了一個check程式,該程式大量建立指定數目的BRUSH對象,並統計本進程的GDI對象數及系統中總的GDI對象數,其運行介面如下(這裡圖顯示不出來,不過無關緊要)

     其中統計系統中總的GDI對象的代碼如下:
int   GetGDINumInSystem(void)
{
     int nGDINums = 0;    /*所有進程的GDI對象之和*/
     int nProcess    = 0;    /*系統中的進程數*/

     DWORD aProID[1024];
     DWORD cbNeeded;
     ::EnumProcesses ( aProID, sizeof(aProID), &cbNeeded );

     /*系統中進程總數*/
     nProcess = cbNeeded / sizeof ( DWORD );

     /*統計每個進程的GDI對象數*/
     for ( INT i=0; i < nProcess; i ++ )
     {
         HANDLE hPro = ::OpenProcess (PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,   FALSE,   aProID[i]   );

         nGDINums += ::GetGuiResources ( hPro, GR_GDIOBJECTS );

         CloseHandle ( hPro );
     }

     return nGDINums;
}

     使用此check程式,做如下實驗:
     ①修改Windows 2000的登錄機碼” GDIProcessHandleQuota”值為12000;
     ②開啟一個check進程,在其中建立11000個GDI對象!
     ③開啟第二個check進程(第一個進程不關閉),在其中建立11000個GDI對象。
     實驗結果表明,第一個進程的對象可以順利建立,而第二個進程的對象則不能順利建立,這就說明,在Windows 2000中,每個進程可以建立的GDI對象數,不僅在進程內部有一定限制,而且還受限於整個作業系統。經多次實驗表明,當Windows 2000系統中總的GDI對象數達到15900以後的某個值後,進程就不能再建立GDI對象了,系統就變得不穩定了,至於該臨界值到底是多少,各次實驗結果並不一致,但是都在15900以後。

     上面的測試是在Windows 2000中進行的,那麼,在Windows 2003中,情況又是什麼樣呢?立即動手,到2003系統中,修改登錄機碼”GDIProcessHandleQuota”為20000,然後運行測試程式並在其中建立20000個GDI對象,一切正常! 再改,30000,運行,仍然正常;……直到改到了70000的時候,系統運行才會出現介面繪製問題,在這樣的事實下,不得不做假設:難道Windows 2003中取消了客戶GDI控制代碼最多16K的限制,而將限制設到了64K?
     為了驗證這個推測,我使用check程式直接建立64K的GDI資源,運行結果表明,當進程建立了一定的GDI對象之後,就不能建立新的GDI對象了,這表明上面的推測不完全正確,不過,基於在Windows 2000中的實驗經驗,很快就想到了在Windows 2003中系統可以建立的GDI對象應該還要受限於作業系統,也就是說:Windows 2003中放寬了每個進程可以建立的GDI對象數目,但是整個系統中GDI對象數不能超過某個值。為驗證此結果,做如下實驗:
    ①修改Windows 2003的登錄機碼” GDIProcessHandleQuota”值為50000;
    ②開啟一個check進程,在其中建立40000個GDI對象!
    ③開啟第二個check進程(第一個進程不關閉),在其中建立40000個GDI對象。
    實驗結果基本上和Windows 2000中的結果類似,唯一不同的是,在第二個進程建立GDI對象的過程中,當系統中總GDI對象達到63700以後的某個值後才會建立失敗。同樣,該臨界值也不固定,不過多次實驗表明,當總的GDI對象數達到63700以後,系統就變得不穩定了。

結論
     經過上面的分析,我們可以知道,在Windows 2000/2003 作業系統中,每個進程可以建立的GDI對象,受限於三個方面因素:系統本身的兩個方面的限制和Windows註冊表中設定的最大值限制。
     首先,每個進程可以建立的GDI對象數,在理論上為64K,但是在Windows 2000中,系統將客戶可以建立的GDI控制代碼數硬設定為不能超過16K,而事實上當GDI對象數達到12K之後,系統即不正常;在Windows 2003中,系統放寬了對GDI對象數的限制,使得每個進程可以使用的GDI對象數接近64K;
    
     其次,每個進程可以建立的GDI對象數,還受限於作業系統中GDI對象總數;在Windows 2000中,當系統中總的GDI對象達到15900以後的某個值後,進程就不能再建立新的GDI對象了;在20003中,這個值增加到63700以後的某個值。但是,到底是哪個值,則不固定。
    
     最後,進程可建立的最大GDI對象數目還受限於註冊表中HKEY_LOCAL_MACHINE/ SOFTWARE/Microsoft/Windows NT/CurrentVersion/Windows下的"GDIProcessHandleQuota" 項所設定的值,該值設定了Windows中每個進程可以建立的最大GDI對象數,在Windows 2000及2003系統中,其預設均為10000,這也就說明了當程式建立的GDI對象數達到9999之後介面為什麼會混亂。

相信,你看了之後,知道是怎麼回事了吧。

總之一句話,建立了GDI/GDI+對象,用完之後,要釋放!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.