Bitmaps: Speicherallokation, Freigabe und Löschung



  • In Deiner WM_PAINT ist auch noch ein grober Schnitzer drin:
    Du erzeugst dort jedesmal einen Speicher-Gerätekontext, löschst diesen aber nie mit DeleteDC, sondern gibst ihn lediglich frei mit ReleaseDC.
    Wenn Du Dein Programm so eine Weile laufen lässt, hast Du bald alle GDI-Objekte des Systems verbraucht. Nicht gut, denn GDI-Objekte sind nicht unbegrenzt vorhanden.

    Für die Performance wäre es auch besser, wenn Du LoadImage und CreateCompatibleDC in die WM_CREATE auslagerst und die Handles global abspeicherst. In der WM_DESTROY löschst Du die beiden dann wieder.

    Gruß
    Greenhorn

    Zuerst einmal vielen lieben Dank für diese tollen Hinweise! Ich habe mich leider erst heute an die Arbeit machen können - folgendes kam dabei heraus (Der Code funktioniert! Ich bin nur unsicher, ob ich alles richtig gemacht habe!):

    // Backgroundpainting
    HBITMAP h1Bitmap;
    HDC h1BitmapDC;
    HDC h1DC;
    
    int Bitmaphoehe;
    int Bitmapbreite;
    
    int PaintBackground(HWND hwnd)
    {
             static PAINTSTRUCT ps;
             static HDC h1DC;
             RECT rect;
             h1DC = BeginPaint( hwnd, &ps );                   // draw the following stuff:
                 GetClientRect(hwnd, &rect);        // Fenstergrösse herausfinden, um in LoadImage einzufügen
                 int hoehe = rect.bottom - rect.top;       
                 int breite = rect.right - rect.left; 
                 BITMAP bmp;
                 GetObject(h1Bitmap,sizeof(bmp),&bmp); 
    
                 SelectObject(h1BitmapDC,h1Bitmap); 
                 StretchBlt(h1DC,0,0, breite, hoehe,h1BitmapDC,0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY); // (hDC, Anfangsposix, AnfangsposiY    
    
                 ReleaseDC(hwnd,h1BitmapDC); 
                 ReleaseDC(hwnd,h1DC); 
                 Bitmaphoehe = bmp.bmHeight;
                 Bitmapbreite = bmp.bmWidth;
                 if(h1Bitmap == NULL)
                 {
                 MessageBox(hwnd, "Could not load Bitmap!", "Error", MB_OK | MB_ICONEXCLAMATION);
                 }
                 Ellipse( h1DC, 300, 100, 330, 130 );               // Ellipse: ( hdc, Anfangspunktx, Anfangspunkty, Endpunktx, Endpunkty)
                 Ellipse( h1DC, 160, 100, 190, 130 );               // Ellipse: ( hdc, Anfangspunktx, Anfangspunkty, Endpunktx, Endpunkty)
                 Rectangle( h1DC, 20, 90, 330, 100 );              // Rectangle: ( hdc, Anfangspunktx, Anfangspunkty, Endpunktx, Endpunkty)
                 Rectangle( h1DC, 220, 70, 300, 90 );              // Rectangle: ( hdc, Anfangspunktx, Anfangspunkty, Endpunktx, Endpunkty)
    
             EndPaint( hwnd, &ps );
    }
    
    // Buttonpainting
    HBITMAP h2Bitmap;          // Bitmap, die später das Image für Buttons aufnehmen wird
    static HWND hButton;       // hButton an dieser Stelle deklariert, da er in int Paintbuttons benötigt wird
    HDC h2BitmapDC;
    HDC h2DC;
    int Checkvariable;         // zum Überprüfen interner Werte in dieser Funktion (momentan abrufbar über Klick auf button1)
    #define FastDT(buffer,rect) DrawText(hdc,buffer,-1,&rect,DT_CENTER|DT_SINGLELINE|DT_VCENTER);
    
    int PaintButtons(HWND hButton)
    {
       static PAINTSTRUCT ps;
       static HDC hdc;
       RECT rect;
       int hoehe;
       int breite;
       char buffer9[256] = "0";
       GetWindowText(hButton, buffer9, 256);
             hdc = BeginPaint(hButton, &ps);
             GetClientRect(hButton, &rect);        // Fenstergrösse herausfinden, um in LoadImage einzufügen
             hoehe = rect.bottom - rect.top;       
             breite = rect.right - rect.left; 
             BITMAP bmp;
             GetObject(h2Bitmap,sizeof(bmp),&bmp); 
      Checkvariable = sizeof(bmp);
             SelectObject(h2BitmapDC,h2Bitmap); 
             StretchBlt(hdc,0,0, breite, hoehe,h2BitmapDC,0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY); // (hDC, Anfangsposix, AnfangsposiY
             // hier war LoadImage h2Bitmap
             SetBkMode (hdc, TRANSPARENT);  // damit der Text nicht in einem weissen Rechteck erscheint
             FastDT(buffer9,rect);      //siehe Makro oben:      DrawText(hdc, buffer9,-1,&rect,DT_CENTER | DT_SINGLELINE | DT_VCENTER);
    
         /*
         n Deiner WM_PAINT ist auch noch ein grober Schnitzer drin: 
    Du erzeugst dort jedesmal einen Speicher-Gerätekontext, löschst diesen aber nie mit DeleteDC, sondern gibst ihn lediglich frei mit ReleaseDC. 
    Wenn Du Dein Programm so eine Weile laufen lässt, hast Du bald alle GDI-Objekte des Systems verbraucht. Nicht gut, denn GDI-Objekte sind nicht unbegrenzt vorhanden. 
    
    Für die Performance wäre es auch besser, wenn Du LoadImage und CreateCompatibleDC in die WM_CREATE auslagerst und die Handles global abspeicherst. In der WM_DESTROY löschst Du die beiden dann wieder.
    */  
             ReleaseDC(hButton,h2BitmapDC); 
             ReleaseDC(hButton,h2DC);
             EndPaint(hButton, &ps );
    
        return Checkvariable;
    }
    

    Anschließend die Aufrufe in der WM_CREATE, in die ich wie in der Kritik gefordert meine LoadImage und meine CreateCompatibleDC ausgelagert habe:

    [ ..... ]
    WM_CREATE
    {
             [ ..... ]
    
             // Background
             h1Bitmap = (HBITMAP)LoadImage(NULL, "test.bmp", IMAGE_BITMAP, 400, 400, LR_LOADFROMFILE);  // Achtung: die beiden Zahlenwerte müssen gleich gross sein, ansonsten kann man sie benutzen um Teile der Bitmap von unten oder von links abzuschneiden.
             h1DC = GetDC(hwnd); 
             h1BitmapDC = CreateCompatibleDC(h1DC); 
    
             // Button Images
             h2Bitmap = (HBITMAP)LoadImage(NULL, "test2.bmp", IMAGE_BITMAP, 40, 40, LR_LOADFROMFILE);  // Achtung: die beiden Zahlenwerte müssen gleich gross sein, ansonsten kann man sie benutzen um Teile der Bitmap von unten oder von links abzuschneiden.
    
             // Buttons         
             h1Button = CreateWindow(  "button",
                                      "Experience",
                                      WS_CHILD | WS_VISIBLE,
                                      0, 0, 100, 30,
                                      hwnd,
                                      NULL,
                                      ((LPCREATESTRUCT) lParam) -> hInstance,
                                      NULL);
    
             h2DC = GetDC(h1Button);                            // Vorbereitung der Buttonwerte. Momentan nur für identische Buttons (Größe, Bitmap)
             h2BitmapDC = CreateCompatibleDC(h2DC); 
    
             [ ..... ]
    }
    

    Dann die Aufrufe in der WM_PAINT:

    case WM_PAINT:
          { 
             // Background zeichnen
             PaintBackground(hwnd);
             // Buttons einfärben durch Funktionsaufruf der zu Programmbeginn deklarierten Funktion PaintButtons():
             PaintButtons(h1Button);
             PaintButtons(h2Button);
             PaintButtons(h3Button);
             PaintButtons(h4Button);
             PaintButtons(h5Button);
             PaintButtons(h6Button);
             PaintButtons(h7Button);
             PaintButtons(h8Button);
    
          }
    

    Und zuletzt die Speicherbereinigung beim Programmende

    case WM_DESTROY:
          {
             del_img();
             PostQuitMessage(0);
    
             // Clean Background Bitmap
             DeleteObject(h1Bitmap); 
             DeleteDC(h1DC);
             DeleteDC(h1BitmapDC);
             // Clean Button Bitmap(s)
             DeleteObject(h2Bitmap);
             DeleteDC(h2DC);
             DeleteDC(h2BitmapDC);
    
             return 0;
          }
    

    Habe ich das soweit richtig gemacht? Also in Bezug auf den Quote vom Anfang? Oder habe ich vielleicht etwas übersehen? (Ich wette darauf 8)( )

    Alles Gute und frohe Pfingsten!


  • Mod

    Also dieser Code ist mit x-Fehlern gesprickt.
    static HDC h1DC; ??? verdeckt globalö h1DC. Gleiches für h2DC.

    Hier kann doch gar nichts funktionieren.

    Typisches GDI leak.

    Wenn Du ein Objektin einen GDI Kontext selektierst (SelectObject), dann gekomst Du das alte Objekt zurück. Dein Objekt bleibt im Kontext und kann nicht freigegeben werden, wenn der Kontext gelöscht wird bzw. wenn Du das Objekt zerstötren willst.

    Immeer den Rückgabewert sichern und am Ende der GDI-Operation wieder das alte Objekt zurück selektieren.

    Waruim bitte sind die PAINTSTRUCTs statisch?



  • Habe es nur kurz überflogen, aber ich würde es eher so machen ...

    // Globals
    HDC     hdcMem ;
    HBITMAP hbmBkGnd ;
    HBITMAP hbmButton [8] ;
    
    [ ..... ]
    
    case WM_CREATE:
    {
    	[ ..... ]
    	POINT pt [8] = {50, 40, ..., ..., usw.} ; // Positionen der Schaltflächen
    
    	// Background
    	hbmBkGnd = (HBITMAP)LoadImage(NULL, "test.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
    	HDC hDC = GetDC(hwnd);
    	hdcMem = CreateCompatibleDC(hDC);
    	ReleaseDC (hwnd, hDC) ;
    
    	// Button Images
    	for (int i = 0 ; i < 8 ; i++ )
    	{
    		hbmButton[i] = (HBITMAP)LoadImage(NULL, "test2.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
    
    		// Buttons
    		hButton = CreateWindow( "button",
    						    "Experience",
    						    WS_CHILD | WS_VISIBLE | BS_BITMAP,
    						    pt[i].x, pt[i].y, 100, 30,
    						    hwnd,
    						    NULL,
    						    ((LPCREATESTRUCT) lParam) -> hInstance,
    						    NULL);
    
    		SendMessage (hButton, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hbmButton[i]) ;
    	}
    	[ ..... ]
    }
    case WM_DESTROY:
    {
    	// Clean Background Bitmap
    	DeleteObject(hbmBkGnd);
    	DeleteDC(hdcMem);
    
    	// Clean Button Bitmap(s)
    	for (int i = 0 ; i < 8 ; i++ )
    	{
    		DeleteObject(hbmButton[i]);
    	}
    
    	PostQuitMessage(0);
    
    	return 0;
    }
    
    [ ..... ]
    
    void PaintBackground(HWND hwnd)
    {
    	PAINTSTRUCT ps;
    	HGDIOBJ hObjOld ;
    	RECT rect;
    	HDC hDC = BeginPaint( hwnd, &ps );
    	GetClientRect(hwnd, &rect);
    
    	BITMAP bmp;
    	GetObject(hbmBkGnd, sizeof(bmp), &bmp);
    
    	hObjOld = SelectObject(hdcMem, hbmBkGnd);
    	StretchBlt(hDC, 0, 0, rect.right, rect.bottom,hdcMem,0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY);
    	SelectObject(hdcMem, hObjOld);
    
    	Ellipse( hDC, 300, 100, 330, 130 );               // Ellipse: ( hdc, Anfangspunktx, Anfangspunkty, Endpunktx, Endpunkty)
    	Ellipse( hDC, 160, 100, 190, 130 );               // Ellipse: ( hdc, Anfangspunktx, Anfangspunkty, Endpunktx, Endpunkty)
    	Rectangle( hDC, 20, 90, 330, 100 );              // Rectangle: ( hdc, Anfangspunktx, Anfangspunkty, Endpunktx, Endpunkty)
    	Rectangle( hDC, 220, 70, 300, 90 );              // Rectangle: ( hdc, Anfangspunktx, Anfangspunkty, Endpunktx, Endpunkty)
    
    	EndPaint( hwnd, &ps );
    }
    

    Ungetestet, können noch Fehler drin sein ...

    Gruß
    Greenhorn



  • Einen Keksbaum @ Martin!

    Vielen lieben Dank für die Hinweise!

    Also dieser Code ist mit x-Fehlern gesprickt.
    static HDC h1DC; ??? verdeckt globalö h1DC. Gleiches für h2DC.

    Hier kann doch gar nichts funktionieren.

    Typisches GDI leak.

    Da mein Code (seltsamerweise) funktionierte, hatte ich die Doppeldeklaration von h1DC übersehen. Außerdem war HDC hdc unnötig, ich habe es entfernt und alle hdc durch h2DC ersetzt.

    Waruim bitte sind die PAINTSTRUCTs statisch?

    Das "static" der Paintstruct war ein Relikt aus der Zeit, als ich mal versucht hatte, die Paintstruct "dauerhaft" das ganze Programm über zu benutzen - und erst beim WM_DESTROY wieder zu entfernen - was natürlich voll in die Hose ... 😃

    PAINTSTRUCT ps;
    

    reicht völlig.

    Wenn Du ein Objektin einen GDI Kontext selektierst (SelectObject), dann gekomst Du das alte Objekt zurück. Dein Objekt bleibt im Kontext und kann nicht freigegeben werden, wenn der Kontext gelöscht wird bzw. wenn Du das Objekt zerstötren willst.

    Immeer den Rückgabewert sichern und am Ende der GDI-Operation wieder das alte Objekt zurück selektieren.

    auf
    http://msdn.microsoft.com/en-us/library/dd162957(v=vs.85).aspx
    habe ich

    The SelectObject function selects an object into the specified device context (DC). The new object replaces the previous object of the same type.

    gefunden, daher ging ich davon aus, dass das neue Objekt das alte ersetzt und dabei das alte gelöscht wird. Nubfragen: Wie sichere ich den Rückgabewert? Mit "das alte Objekt zurückselektieren" meinst du sicher eben diesen gesicherten Rückgabewert, den ich selektieren muss?

    Kekskiste @ Greenhorn!

    Super Ideen!

    Die mit dem point pt array hebe ich mir auf jeden Fall für später auf, solange ich mir noch nicht sicher über die Positionen der Buttons bin!

    Besonders gefällt mir die Idee, den Buttons schon bei Createwindow dauerhaft die Images zuzuweisen!

    Den Background in eine void am Programmende zu stecken werde ich auf jeden Fall auch ausprobieren!

    Vielen lieben Dank euch zweien, ich melde mich wenn ich fertig bin (oder(wahrscheinlicher) versage 8)( )!


Anmelden zum Antworten