Zum Inhalt springen

[Cpp] Speicherüberlauf (Vermutlich irgendwelche leaks)


Empfohlene Beiträge

Hi leute,

da mir der Winapi Wrapper im .NET an sich zu langsam war beziehungsweise ich an die performance grenzen vom .NET gestoßen bin, habe ich mich entschieden das ganze innerhalb einer Cpp dll abzuwickeln und im .NET mit dem Result zu arbeiten.

Das heißt der "Screenshot" sowie diverse algorithmen zum verkleinern und konvertieren wurden in die Cpp dll ausgelagert.

Die C# dll ruft dann die entsprechende Funktion auf und bekommt einen Pointer (HBITMAP) zurück der auf das Bild zeigt.

Den erhaltenen Performanceschub will ich nichtmehr missen (pure .NET 20 fps / Cpp 30 fps).

Allerdings bringt mir das kein Stück wenn ich nach 10 Sekunden im .NET ne Out of Memory Fehlermeldung erhalte.

Jetzt bin ich mir aber nicht so ganz sicher woran das liegt.

Da das Image innerhalb des StackFrames meiner cpp dll generiert wird, vermute ich das das .NET dieses Objekt nicht freigeben kann. Aber wenn das stimmen würde, hätte ich ja längt eine MemoryViolation Exception erhalten.

Was denkt ihr woran das liegen kann?

Hier mal eine simple Funktion zum generieren eines normalen Bitmaps (auch hier tritt der fehler auf) in der cpp dll.


_extern HBITMAP GetDesktopImageExt(){

HWND capture = GetDesktopWindow(); 

 if(!IsWindow(capture)) return NULL; 


 RECT rect; 

 GetWindowRect(capture, &rect); 


 size_t dx = rect.right - rect.left; 

 size_t dy = rect.bottom - rect.top; 


 BITMAPINFO info; 

 info.bmiHeader.biSize          = sizeof(BITMAPINFOHEADER); 

 info.bmiHeader.biWidth         = dx; 

 info.bmiHeader.biHeight        = dy; 

 info.bmiHeader.biPlanes        = 1; 

 info.bmiHeader.biBitCount      = 24; 

 info.bmiHeader.biCompression   = BI_RGB; 

 info.bmiHeader.biSizeImage     = 0; 

 info.bmiHeader.biXPelsPerMeter = 0; 

 info.bmiHeader.biYPelsPerMeter = 0; 

 info.bmiHeader.biClrUsed       = 0; 

 info.bmiHeader.biClrImportant  = 0; 


 HBITMAP bitmap = 0; 

 BYTE*   memory = 0; 


 HDC device = GetDC(capture); 

 bitmap = CreateDIBSection(device, &info, DIB_RGB_COLORS, (void**)&memory, 0, 0);

  ReleaseDC(capture, device); 

 if(!bitmap || !memory) return NULL; 


 HDC winDC = GetWindowDC(capture); 

 HDC memDC = CreateCompatibleDC(winDC); 

 SelectObject(memDC, bitmap);

 //HGDIOBJ obj = SelectObject(memDC, bitmap); 

 BitBlt(memDC, 0, 0, dx, dy, winDC, 0, 0, SRCCOPY); 

 //DeleteObject(obj);

 DeleteDC(memDC); 

 ReleaseDC(capture, winDC); 

 return bitmap ;

}

Möglicherweise hab ich auch einfach ein Release/Delete vergessen...

Ich steh grad ein wenig auf dem Schlauch.

Ich hatte mir kurzeitig üblegt ob ich eine externe Funktion definiere, welcher ich den Pointer übergebe und das HBITMAP wieder innerhalb der Cpp dll release.

Ach keine Ahnung irgendwie ist grad alles leer im oberstübchen...

lg

Gateway

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hi,

hat sich erledigt.

Es verhält sich so wie ich es mir dachte.

Ich hätte es doch testen sollen bevor ich den Thread verfasste.

Aber trotzdem sehr lehrreich.

Es sieht wie folgt aus:

Das Bitmap wird innerhalb des Stack Frames der Cpp Funktion erstellt.

Dieser Stack Frame befindet sich natürlich innerhalb des Stack Frames der Cpp Library.

Das .NET greift den Pointer ab.

Mit der .NET Funktion Image.FromHBitmap wird die Struktur Image gefüllt mit den referenzierten Daten die immernoch innerhalb des Stack Frames der Cpp Funktion liegen.

Wenn ich ein nun im .NET ein Dispose() auf das Image Objekt absetzte passiert nichts, da das Framework keine Zugriffsberechtigung auf den Speicherbereich meiner Cpp Library hat.

Jetzt habe ich der Cpp Library noch eine Funktion spendiert die ein HBITMAP als parameter verlangt.

Innerhalb dieser Funktion mach ich nun ein DeleteObject(BitmapPointer).

Diese Funktion pInvoke ich im .Net nachdem ich das Image geklont habe.

Und siehe da keine Problem mehr ......

lg

Gateway

Link zu diesem Kommentar
Auf anderen Seiten teilen

Das Bitmap wird innerhalb des Stack Frames der Cpp Funktion erstellt.

Das Handle wird dort erstellt. Die Bitmapdaten befinden sich nicht auf dem Stack.

Mit der .NET Funktion Image.FromHBitmap wird die Struktur Image gefüllt mit den referenzierten Daten
FromHBitmap kopiert die Bilddaten, das Image-Objekt enthält danach keinen Verweis auf die GDI-Bilddaten.

Wenn ich ein nun im .NET ein Dispose() auf das Image Objekt absetzte passiert nichts, da das Framework keine Zugriffsberechtigung auf den Speicherbereich meiner Cpp Library hat.
Wenn du Dispose aufrufst, wird die Kopie freigegeben, die FromHBitmap erzeugt hat. Da kein Verweis auf die ursprünglichen Daten besteht, kann auch nicht mehr passieren.

Jetzt habe ich der Cpp Library noch eine Funktion spendiert die ein HBITMAP als parameter verlangt.

Innerhalb dieser Funktion mach ich nun ein DeleteObject(BitmapPointer).

Warum rufst du nicht direkt DeleteObject auf?
Link zu diesem Kommentar
Auf anderen Seiten teilen

Das Handle wird dort erstellt. Die Bitmapdaten befinden sich nicht auf dem Stack.

Sry ich meinte den Stack Frame der Funktion die ich aufrufe :).

Die Bitmap daten befinden sich auf dem Heap das ist schon klar.

FromHBitmap kopiert die Bilddaten, das Image-Objekt enthält danach keinen Verweis auf die GDI-Bilddaten.

Bist du dir da sicher? Ich hab nämlich doch einen AccessViolation Exception erhalten. Nur wird die in System.Drawing gehandelt. Steht also lediglich im Output fenster.

Warum rufst du nicht direkt DeleteObject auf?

Das habe ich bereits versucht. Jedoch habe ich dann eine Null Reference Exception seitens .NET bekommen.

Ist ja auch irgendwo klar.

Wenn ich innerhalb der Cpp Funktion das Bitmap erstelle um es am Ende der Funktion wieder freizugeben und das .NET dann versuch mit dem Pointer die Bitmap daten abzugreifen muss es ja unweigerlich krachen.

lg

Gateway

Link zu diesem Kommentar
Auf anderen Seiten teilen

Bist du dir da sicher?

Zumindest behauptet das die Doku: Image.FromHbitmap Method (IntPtr) (System.Drawing)

Das habe ich bereits versucht. Jedoch habe ich dann eine Null Reference Exception seitens .NET bekommen.

Ok, das war missverständlich formuliert. Mit "direkt" meinte ich nicht innerhalb deiner C++-Funktion, sondern direkt aus .NET, ohne den Umweg über einen Wrapper. Der richtige Zeitpunkt dafür wäre nach FromHBitmap.
Link zu diesem Kommentar
Auf anderen Seiten teilen

Genau das mach ich momentan :) Deswegen funktionierts jetzt ja auch :).

So sieht ein beispielaufruf in C# aus.


                Image result = null;

                IntPtr ptr = GetDesktopImageExt(biPlanes, biBitCount, (int)compression, biSizeImage, biXPelsPerMeter, biYPelsPerMeter, biClrUsed, biClrImportant);

                if (ptr != IntPtr.Zero)

                {

                    result = (Image)Image.FromHbitmap(ptr).Clone();

                    ReleaseHBitmap(ptr);

                    if (includeCursor)

                        result = Utility.ImageUtil.CaptureScreen.IncludeCurrentCursor(result);

                }

                return result;

Lg und Danke

Gateway

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hi,

ich muss das Thema leider noch mal in den Fokus rücken.

Das Problem mit den obigen Funktionen ist behoben.

Zwar schauckelt er sich teilweise recht hoch mit der Speichernutzung, jedoch gibt er es brav wieder frei.

Jetzt habe ich ein paar Testläufe ín Cpp gestartet unter Verwendung von GDI.

Hier besteht leider wieder das Problem und es ist nicht mit der vorherigen Lösung behoben.

Ich erhalte wieder in C# eine Out of Memory Exception und ich vermute stark das es an der Cpp Implementierung liegt.

GdiCore.h


#include <Windows.h>

#include <GdiPlus.h>

#pragma once

using namespace Gdiplus;

using namespace std;

class GdiCore

{

private:

	Bitmap* bmp;

	GdiplusStartupInput gdiplusStartupInput; 

    ULONG_PTR gdiplusToken;   

	HPALETTE* palette;

	bool hDeleted;

public:

	~GdiCore(void);

	GdiCore(HBITMAP input);

	//diverse andere Grafikfunktionen

        //...

	//diverse andere Grafikfunktionen

	HBITMAP Resize(int width, int height);

	void Release();

};


GdiCore.cpp:
 

#include "StdAfx.h"

#include "GdiCore.h"



GdiCore::GdiCore(HBITMAP input)

{

	GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); 

	palette = new HPALETTE();

	bmp = new Bitmap(input,*palette);

}



GdiCore::~GdiCore(void)

{

	if (!hDeleted )

		Release();

}

HBITMAP GdiCore::Resize(int width, int height){

	HBITMAP result = NULL;

	UINT o_height = bmp->GetHeight();

    UINT o_width = bmp->GetWidth();

    int n_width = width;

    int n_height = height;

    double ratio = ((double)o_width) / ((double)o_height);

    if (o_width > o_height) {

        n_height = static_cast<int>(((double)n_width) / ratio);

    } else {

        n_width = static_cast<int>(n_height * ratio);

    }

    Bitmap* newBitmap = new Bitmap(n_width, n_height, bmp->GetPixelFormat());

    Graphics graphics(newBitmap);

    graphics.DrawImage(bmp, 0, 0, n_width, n_height);

	newBitmap->GetHBITMAP(NULL,&result);

	return result;

}

void GdiCore::Release(){

	if (!hDeleted ){

	//DeleteObject(bmp);

	delete palette,bmp;

	GdiplusShutdown(gdiplusToken);

	hDeleted = true;

	}

}

Die Funktion die ich aus C# Aufrufe ist wie folgt definiert:

_extern HBITMAP ResizeImageExt(HBITMAP bmp, int width, int height){

	HBITMAP result = NULL;

	GdiCore* core = new GdiCore(bmp);

	result = core->Resize(width,height);

	core->Release();

	delete core;

	return result;

}

Hier ist der C# Sharp Aufruf:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Runtime.InteropServices;

using System.Drawing;


namespace Utility.Interop.dGDI

{

    public sealed class ImageUtilExt

    {

        #region pInvoke

        [DllImport("dGDI.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]

        private static extern IntPtr ResizeImageExt(IntPtr bmp, int width, int height);

        #endregion

        #region Static Functions

        public static Image ResizeImage(Bitmap input, int width, int height) {

            Image result = null;

            if (input == null)

                return null;

            IntPtr src = IntPtr.Zero;

            try

            {

                src = input.GetHbitmap();

            }

            catch {

                return (Image)input;

            }

            IntPtr ptr = ResizeImageExt(src, width, height);

            if (ptr != IntPtr.Zero)

            {

                Image ImgCache = Image.FromHbitmap(ptr);

                result = (Image)(ImgCache.Clone());

                bool bRelease = ScreenCaptureExt.ReleaseHBitmap(ptr);

                System.Diagnostics.Trace.WriteLine(bRelease.ToString());

                ImgCache.Dispose();

                ImgCache = null;

            }

            return result;

        }

        #endregion

    }

}

Hat jemand von euch eine Vermutung woran es liegen könnte?

lg

Gateway

Link zu diesem Kommentar
Auf anderen Seiten teilen

using namespace Gdiplus;

using namespace std;

Using-Direktiven sollten nicht im Header stehen.

HPALETTE* palette;

Abgesehen davon, dass du es ohne Grund mit new anlegst, zeigt dieses Handle niemals auf eine tatsächlich existierende GDI-Palette. Wozu ist das überhaupt drin?

bool hDeleted;

Wozu dieser bool und Release()? Warum macht das nicht einfach der Destruktor?

Bitmap* newBitmap = new Bitmap(n_width, n_height, bmp->GetPixelFormat());

wird geleakt. Warum überhaupt mit new, und nicht auf dem Stack?

delete palette,bmp;
Damit gibst du nur palette frei. Der delete-operator bindet stärker als der Kommaoperator. Wenn es andersherum wäre, würde übrigens nur bmp freigegeben. Du kommst nicht drumherum, zweimal delete zu schreiben.

GdiCore* core = new GdiCore(bmp);
Warum new?

Ganz allgemein ist in C++ jedes new, das nicht sofort in einem Smartpointer landet, verdächtig.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Using-Direktiven sollten nicht im Header stehen.

Jup wird geändert.

Abgesehen davon, dass du es ohne Grund mit new anlegst, zeigt dieses Handle niemals auf eine tatsächlich existierende GDI-Palette. Wozu ist das überhaupt drin?

Das ist ne Leiche aus einer hier nicht aufgeführt Funktion.

Hab ich jetzt mal rauskommentiert.

Wozu dieser bool und Release()? Warum macht das nicht einfach der Destruktor?

Irgendwie hatte ich das Gefühl das der Desktruktor nicht aufgerufen wurde.

Also hab ich Release() geschrieben und für den Fall das er doch in den Desktruktor gehen sollte, sollte er nicht versuchen doppelt das delete auszuführen deswegen der boolean.

wird geleakt. Warum überhaupt mit new, und nicht auf dem Stack?

Weil das teilweise enorm großen Bitmaps sein können und ich eigentlich immer der Meinung war das große Objekte nichts auf dem Stack zu suchen haben.

Damit gibst du nur palette frei. Der delete-operator bindet stärker als der Kommaoperator. Wenn es andersherum wäre, würde übrigens nur bmp freigegeben. Du kommst nicht drumherum, zweimal delete zu schreiben.

:D Danke ich dachte ich versuchs mal und da der Debugger das nicht als Syntaxfehler angekreidet hat, dachte ich er hats gefressen.

@all:

Seid ihr euch sicher das hier:

Bitmap* newBitmap = new Bitmap(n_width, n_height, bmp->GetPixelFormat());

Der Leak stattfindet?

Ihr dürft die Zusammenhänge nicht vergessen.

In der Resize Funktion wird ein neues Bitmap auf dem Heap generiert (newBitmap).

Dann hol ich mir den Pointer der auf die Bitmapdaten zeigt


newBitmap->GetHBITMAP(NULL,&result);

Ich kann innerhalb dieser Funktion unmöglich das Bitmap löschen da sonst C# die Bitmapdaten nicht abgreifen kann. Jetzt sieht es ja so aus das C# die Bitmapdaten mithilfe des Pointers in ein neues Image Objekt kopiert.

 Image ImgCache = Image.FromHbitmap(ptr);

Dannach wird mit hilfe der Funktion ReleaseHBitmap der Pointer an meine Cpp Funktion "ReleaseHBitmap" übergeben um dort das Bitmap zu löschen.

ScreenCaptureExt.ReleaseHBitmap(ptr)

Dort wird dann das Bitmap zerstört:

_extern bool ReleaseHBitmap(HBITMAP hbitmap){

return DeleteObject(hbitmap );

}

Link zu diesem Kommentar
Auf anderen Seiten teilen

Weil das teilweise enorm großen Bitmaps sein können und ich eigentlich immer der Meinung war das große Objekte nichts auf dem Stack zu suchen haben.
Der vom Objekt verwaltete Speicher liegt nicht auf dem Stack, egal wo du es anlegst. Kann auch gar nicht, weil die Größe von Stackvariablen zur Compilezeit feststeht. Lass dir mal sizeof(Bitmap) ausgeben, dann siehst du, wieviel Stackspeicher das verbraucht. Die Klasse selbst legt den Speicher für die Daten auf dem Heap an.

:D Danke ich dachte ich versuchs mal und da der Debugger das nicht als Syntaxfehler angekreidet hat, dachte ich er hats gefressen.
Hat er auch, der Code tut nur nicht das, was du erwartest. Der wundersame Komma-Operator wertet seine Operanden aus und gibt den rechten zurück.

Seid ihr euch sicher das hier:

Der Leak stattfindet?

Du hast da ein new ohne delete -> Leak.

Ich kann innerhalb dieser Funktion unmöglich das Bitmap löschen da sonst C# die Bitmapdaten nicht abgreifen kann.

Vermutet oder geprüft? Soweit ich weiß, kopiert GetHBITMAP die Bilddaten.
Link zu diesem Kommentar
Auf anderen Seiten teilen

Hi,

ich hab es jetzt auf zwei Wegen versucht.

Zum einen habe ich versucht das "newBitmap" per delete zu zerstören.

Das hat den Leak nicht verhindert.

Letztendlich hab ich fast alles geändert was du bemängelt hast und siehe da es funktioniert.

Die entsprechenden Konstrukte sehen jetzt wie folgt aus:


#include "StdAfx.h"

#include "GdiCore.h"



GdiCore::GdiCore(HBITMAP input)

{

	GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); 

	palette = new HPALETTE();

	bmp = new Bitmap(input, *palette);

}



GdiCore::~GdiCore(void)

{

	//DeleteObject(bmp);

	delete palette;

	delete bmp;

	GdiplusShutdown(gdiplusToken);

}

HBITMAP GdiCore::Resize(int width, int height){

	HBITMAP result = NULL;

	UINT o_height = bmp->GetHeight();

    UINT o_width = bmp->GetWidth();

    int n_width = width;

    int n_height = height;

    double ratio = ((double)o_width) / ((double)o_height);

    if (o_width > o_height) {

        n_height = static_cast<int>(((double)n_width) / ratio);

    } else {

        n_width = static_cast<int>(n_height * ratio);

    }

    //Bitmap* newBitmap = new Bitmap(n_width, n_height, bmp->GetPixelFormat());

    Bitmap newBitmap(n_width, n_height, bmp->GetPixelFormat());

	Graphics graphics(&newBitmap);

    graphics.DrawImage(bmp, 0, 0, n_width, n_height);

	newBitmap.GetHBITMAP(NULL,&result); 

	//DeleteObject(newBitmap);

	//delete newBitmap;

	return result;

}

Ich bin begeistert. Vielen Dank Klotzkopp.

Du warst wie immer eine enorme Hilfe :valen.

lg

Gateway

Link zu diesem Kommentar
Auf anderen Seiten teilen

Dein Kommentar

Du kannst jetzt schreiben und Dich später registrieren. Wenn Du ein Konto hast, melde Dich jetzt an, um unter Deinem Benutzernamen zu schreiben.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung wiederherstellen

  Nur 75 Emojis sind erlaubt.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Editor leeren

×   Du kannst Bilder nicht direkt einfügen. Lade Bilder hoch oder lade sie von einer URL.

Fachinformatiker.de, 2024 by SE Internet Services

fidelogo_small.png

Schicke uns eine Nachricht!

Fachinformatiker.de ist die größte IT-Community
rund um Ausbildung, Job, Weiterbildung für IT-Fachkräfte.

Fachinformatiker.de App

Download on the App Store
Get it on Google Play

Kontakt

Hier werben?
Oder sende eine E-Mail an

Social media u. feeds

Jobboard für Fachinformatiker und IT-Fachkräfte

×
×
  • Neu erstellen...