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ß
GreenhornZuerst 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!
-
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 ichThe 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)( )!