Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi Memory Leak nach Zuweisung eines Resourcestream (https://www.delphipraxis.net/210749-memory-leak-nach-zuweisung-eines-resourcestream.html)

Peter-Pascal 4. Jun 2022 10:32

Memory Leak nach Zuweisung eines Resourcestream
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo,

ich hoffe in dieser Rubrik richtig zu sein.

Meine Frage gilt folgender Zuweisung und anschließendem Memory Leak:
Delphi-Quellcode:
procedure TFormViewFinMathZins.FormCreate(Sender: TObject);
begin
  Image1.Bitmap.LoadFromStream(TResourceStream.Create(HInstance, 'EWoZZ', RT_RCDATA));
end;
Nach Beendigung des Programms bekomme ich beigefügte Meldung über unerwartete (schwarze) Löcher im Speicher. Das leuchtet mir schon ein, aber wie bekomme ich das Loch gestopft?

Wähle ich folgende Variante:
Delphi-Quellcode:
procedure TFormViewFinMathZins.FormCreate(Sender: TObject);
var
  Stream: TStream;
begin
  Stream:= TResourceStream.Create(HInstance, 'EWoZZ', RT_RCDATA);
  Image1.Bitmap.LoadFromStream(stream);
  FreeAndNil(stream)
end;
habe ich logischerweise das Problem nicht.
Meine Frage: Ist es möglich bei der ersteren (eleganteren) Variante die Ressource zu beenden?

Vielen Dank für eure Mühe und
viele Grüße Peter

peterbelow 4. Jun 2022 10:41

AW: Memory Leak nach Zuweisung eines Resourcestream
 
Die zweite Variante ist die richtige, allerdings solltest Du das mit einem try finally Block machen.

Delphi-Quellcode:
procedure TFormViewFinMathZins.FormCreate(Sender: TObject);
var
  Stream: TStream;
begin
  Stream:= TResourceStream.Create(HInstance, 'EWoZZ', RT_RCDATA);
  try
    Image1.Bitmap.LoadFromStream(stream);
  finally
    stream.free;
  end;
end;
Du kannst Dir allerdings den stream sparen, TBitmap hat auch eine LoadFromResourceName-Methode...

mytbo 4. Jun 2022 11:31

AW: Memory Leak nach Zuweisung eines Resourcestream
 
Zitat:

Zitat von Peter-Pascal (Beitrag 1506743)
Meine Frage: Ist es möglich bei der ersteren (eleganteren) Variante die Ressource zu beenden?

Image.Bitmap.LoadFromResourceName(HInstance, 'EWoZZ');

Nachtrag: Peter hat schon den richtigen Hinweis gegeben.

Bis bald...
Thomas

Peter-Pascal 4. Jun 2022 11:46

AW: Memory Leak nach Zuweisung eines Resourcestream
 
Vielen Dank an Peter und Thomas,

es sind gleich einige nützliche Tipps. Danke dafür.

Ich tue mich bei den Try .. finaly Blöcke etwas schwer und bin eher versucht alles mit Try-Blöcken zu schützen, als zu wenig. Dann frage ich mich, ist es nicht auch Ressourcen-Verschwendung?

Try-Blöcke sehe ich voll und ganz ein, bei Fällen wie oben. Also laden von
-Bildern
-Formulare
-Ressourcen allgemein: Dateien

Haben Try-Blöcke Grenzen?

Darf ich noch eine Frage stellen?

Ich muss in mein kleines FinMath-Programm viele Formeln als PNG laden. Ist dies über "Ressourcen und Bilder" der richtige Weg oder gibt es einen besseren, hinsichtlich Ressourcen schonen? Ich weiß nie so richtig, wonach ich entscheiden soll. Gerade wenn ein Programm viele Graphiken (PNG) anzeigen soll bläht das Programm sehr stark auf. Wie geht man vor?

Vielen Dank noch mal

TurboMagic 4. Jun 2022 12:02

AW: Memory Leak nach Zuweisung eines Resourcestream
 
Hallo,

du kannst die PNGs ja auch als Dateien mitliefern und diese laden.
z. B. in ein TImage oder TBitmap etc.

Dann sind die mal nicht in der exe-Datei drin.

Grüße
TurboMagic

mytbo 4. Jun 2022 12:10

AW: Memory Leak nach Zuweisung eines Resourcestream
 
Zitat:

Zitat von Peter-Pascal (Beitrag 1506751)
Wie geht man vor?

Was ist für dich viel? Du könntest deine Ressourcen in eine "Resourcen-DLL" auslagern, oder eine ZIP Datei verwenden. Wenn die Datenquelle schon komprimiert ist, kannst du sie auch ohne zweite Komprimierung in einer ZIP Datei ablegen. Wird noch mehr Funktionalität benötigst, wäre eine SQLite Datenbank als Datengrab eine Überlegung wert.

Bis bald...
Thomas

Peter-Pascal 4. Jun 2022 12:12

AW: Memory Leak nach Zuweisung eines Resourcestream
 
ja, von Datei ist eine Möglichkeit und wenn man nicht möchte, dass der User die Dateien selber nutzt, wäre vielleicht eine verschlüsselte Zip-Datei eine Möglichkeit. ZIP klingt gut.
Wird das Programm so nicht langsam?

Ich frage mich gerade, wie machen das eigentlich Spieleprogrammierer? Viele Texturen sind schließlich nachwievor Bilder.

Du hast recht Thomas, es kommt tatsächlich auf die Menge an. Möglicherweise nutzen Spieleprogrammierer wohl eine SQLite Datenbank.

Wie mache ich eine Reccourcen-DLL?

Wie so oft, es kommt wohl auf den Einzelfall an.

Danke TurboMagic und Thomas

mytbo 4. Jun 2022 12:24

AW: Memory Leak nach Zuweisung eines Resourcestream
 
Zitat:

Zitat von Peter-Pascal (Beitrag 1506756)
Wird das Programm so nicht langsam?

Was ist für dich schnell? Du kannst dir hier zur Orientierung ein paar Werte anschauen. Mit einem weniger sicheren Algorithmus ist es noch viel schneller.

Infos zum Thema "Resource-DLL" findest du hier im Forum.

Bis bald...
Thomas

himitsu 4. Jun 2022 12:35

AW: Memory Leak nach Zuweisung eines Resourcestream
 
wie:

eine leere DLL erstellen
und da deine Ressourcen rein

Manuell über eigene RES, bzw. RC, via
Delphi-Quellcode:
{$R ...}
, bzw.
Delphi-Quellcode:
{$RESOURCE ...}
,
und/oder über die Projekt-Resource (Mainmenü > Projekt > Ressourcen und Bilder)


und dann beim Laden statt HInstance (dem Handle der EXE), das Handle der DLL verwenden.
LoadLibrary oder MSDN-Library durchsuchenLoadLibraryEx + LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE oder LOAD_LIBRARY_AS_DATAFILE
oder GetModuleHandle, wenn die DLL statisch eingebunden wurde, bzw. bereits geladen ist.

Peter-Pascal 4. Jun 2022 12:53

AW: Memory Leak nach Zuweisung eines Resourcestream
 
Danke Himitsu,
klingt gut, aber auch kompliziert.
Ich habe mir Deine Antwort kopiert, um es auszuprobieren.
Könnte eine weitere gute Möglichkeit sein, Bilder in einem Programm zu verwenden. Neben ZIP und SQLite.
Mit dem Link von Thomas bekomme ich eine Orientierung.

Am liebsten wäre mir ja SVG, aber das hat halt Grenzen. Ich denke in vielen Fällen führt kein Weg an Pixel-Bilder vorbei.

himitsu 4. Jun 2022 13:11

AW: Memory Leak nach Zuweisung eines Resourcestream
 
Ob in die EXE oder in eine DLL macht technisch keinen Unterschied. Man braucht nur das Handle des entsprechenden Moduls.
in Delphi ist HInstance das Handle der EXE/DLL, wo der Code drin ist. (aufpassen, wenn mit Packages compiliert)


Hier im Forum suchensvg / Bei Google suchendelphi svg
https://en.delphipraxis.net/topic/33...ort-in-delphi/

Peter-Pascal 4. Jun 2022 21:31

AW: Memory Leak nach Zuweisung eines Resourcestream
 
noch mal vielen Dank,
der Link zum SVG ist sehr wertvoll

Mavarik 7. Jun 2022 16:53

AW: Memory Leak nach Zuweisung eines Resourcestream
 
Also ich mache das schon immer so...

Ich packe alle Resourcen per zlib und binde diese ein...

Ob in einer DLL oder in der Exe ist fast egal, es sei den, Du brauchst die Speicherkachel von ~2GB (3GB mit flag) voll und ganz für die exe...

Mavarik

Rolf Frei 7. Jun 2022 17:13

AW: Memory Leak nach Zuweisung eines Resourcestream
 
Also eine Resourcen DLL ist so ziemlich das Dümmste was du machen kannst. Ausser dass du deutlich mehr Aufwand hast (2 Projektet die jeweils kompiliert werden, 2 Dateien die ausgeleifert werden müssen, etc.) bringt das überhaupt keine Voreteile. Du kannst die Bilder problemlos in deine EXE linken. Die Resoucen werden erst geladen, wenn du sie explizit lädst. Also mit Resourcensparen hat das nichts zu tun.

Mavarik 8. Jun 2022 02:07

AW: Memory Leak nach Zuweisung eines Resourcestream
 
Zitat:

Zitat von Rolf Frei (Beitrag 1506964)
Die Resoucen werden erst geladen, wenn du sie explizit lädst. Also mit Resourcensparen hat das nichts zu tun.

Das ist natürlich nicht so...
Die Resourcen sind immer da...
Vielleicht verwechselst Du das mit den Resourcen die dann oben drauf nochmal für die Bilder benötigt werden...
Natürlich macht es Sinn Resourcen in eine DLL auszulagern...

1.) DLL Laden
2.) Resourcen extrahieren
3.) DLL wieder aus dem Speicher werfen...

Somit bleibt fürs Hauptprogramm wieder der volle Speichern.

Peter-Pascal 8. Jun 2022 12:03

AW: Memory Leak nach Zuweisung eines Resourcestream
 
Hallo Mavarik,

habe ich Dich richtig verstanden, Du bindest alle Bilder (png, jepeg ua) in eine Zip-Datei und holst sie dann bei Bedarf aus der Datei?

Das ist vermutlich hinsichtlich benötigten Speicherplatzes auf der HDU die beste Variante.

Gruß Peter

himitsu 8. Jun 2022 12:08

AW: Memory Leak nach Zuweisung eines Resourcestream
 
@Mavarik / Rolf Frei:

Jupp, länger "ungenutzter" Speicher kann von Windows aus dem RAM (ühysischer Speicher) geworfen werden,
aber im virtuellen Programmspeicher bleibt es dennoch vorhanden und belegt ihn (bei 32-Bit sind die normalen 2GB schnell voll)
Resourcen-DLL wieder entladen, macht auch da den Platz wieder frei.

mytbo 8. Jun 2022 18:11

AW: Memory Leak nach Zuweisung eines Resourcestream
 
Zitat:

Zitat von Peter-Pascal (Beitrag 1507024)
habe ich Dich richtig verstanden, Du bindest alle Bilder (png, jepeg ua) in eine Zip-Datei und holst sie dann bei Bedarf aus der Datei?

Als Beispiel habe ich dir eine Umsetzung mit mORMot geschrieben.

Disclaimer: Das Beispiel ist ein Proof of Concept, der Sourcecode ist weder getestet noch optimiert.
Delphi-Quellcode:
uses
  mormot.core.base,
  mormot.core.data,
  mormot.core.text,
  mormot.core.os,
  mormot.core.zip,
  mormot.core.perf,
  mormot.crypt.core;

type
  TImageResourceFile = class(TObject)
  private
    FPassword: RawUtf8;
    FFileName: TFileName;
    FIsWritable: Boolean;
  protected
    function LoadStream(pmStream: TCustomMemoryStream; const pmcResName: TFileName; const pmcPassword: RawUtf8): Boolean;
    procedure SaveStream(pmStream: TCustomMemoryStream; const pmcResName: TFileName; const pmcPassword: RawUtf8; pmIsCompressed: Boolean = True);
  public
    constructor Create(const pmcFileName: TFileName; const pmcPassword: RawUtf8);
    destructor Destroy; override;
    function LoadImage(pmImage: TImage; const pmcImageName: String): Boolean;
    procedure SaveImage(pmImage: TImage; const pmcImageName: String); overload;
    procedure SaveImage(pmImageData: TCustomMemoryStream; const pmcImageName: String); overload;
  end;

constructor TImageResourceFile.Create(const pmcFileName: TFileName; const pmcPassword: RawUtf8);
begin
  inherited Create;
  FFileName := pmcFileName;
  FPassword := pmcPassword;
  FIsWritable := IsDirectoryWritable(ExtractFilePath(FFileName));
end;

destructor TImageResourceFile.Destroy;
begin
  FillZero(FPassword);
  inherited Destroy;
end;

function TImageResourceFile.LoadStream(pmStream: TCustomMemoryStream; const pmcResName: TFileName; const pmcPassword: RawUtf8): Boolean;
var
  idx: Integer;
  zipRead: TZipRead;
  encStream: TMemoryStream;
  aesReader: TAesPkcs7Reader;
begin
  Result := False;
  if pmStream = Nil then Exit; //=>
  if pmcResName = '' then Exit; //=>
  if not FileExists(FFileName) then Exit; //=>

  zipRead := TZipRead.Create(FFileName);
  try
    idx := zipRead.NameToIndex(pmcResName);
    if idx < 0 then Exit; //=>

    if pmcPassword = '' then
      Result := zipRead.UnZip(idx, pmStream)
    else
    begin
      encStream := TMemoryStream.Create;
      try
        if zipRead.UnZip(idx, encStream) then
        begin
          encStream.Position := 0;
          aesReader := TAesPkcs7Reader.Create(encStream, pmcPassword);
          try
            Result := (StreamCopyUntilEnd(aesReader, pmStream) > 0);
          finally
            aesReader.Free;
          end;
        end;
      finally
        encStream.Free;
      end;
    end;
  finally
    zipRead.Free;
  end;
end;

procedure TImageResourceFile.SaveStream(pmStream: TCustomMemoryStream; const pmcResName: TFileName; const pmcPassword: RawUtf8; pmIsCompressed: Boolean);
const
  IS_COMPRESSED: array[Boolean] of Integer = (0, 6);
var
  zipWrite: TZipWrite;
  encStream: TMemoryStream;
  aesWriter: TAesPkcs7Writer;
begin
  if pmStream = Nil then Exit; //=>
  if pmcResName = '' then Exit; //=>
  if not FIsWritable then Exit; //=>

  if FileExists(FFileName) then
    zipWrite := TZipWrite.CreateFromIgnore(FFileName, [pmcResName])
  else
    zipWrite := TZipWrite.Create(FFileName);

  try
    if pmcPassword = '' then
      zipWrite.AddDeflated(pmcResName, pmStream.Memory, pmStream.Size, IS_COMPRESSED[pmIsCompressed], DateTimeToFileDate(Now))
    else
    begin
      encStream := TMemoryStream.Create;
      try
        aesWriter := TAesPkcs7Writer.Create(encStream, pmcPassword);
        try
          pmStream.Position := 0;
          StreamCopyUntilEnd(pmStream, aesWriter);
          aesWriter.Finish;
        finally
          aesWriter.Free;
        end;

        zipWrite.AddDeflated(pmcResName, encStream.Memory, encStream.Position, IS_COMPRESSED[pmIsCompressed], DateTimeToFileDate(Now));
      finally
        encStream.Free;
      end;
    end;
  finally
    zipWrite.Free;
  end;
end;

function TImageResourceFile.LoadImage(pmImage: TImage; const pmcImageName: String): Boolean;
var
  tmpStream: TMemoryStream;
begin
  Result := False;
  if pmImage = Nil then Exit; //=>
  if pmcImageName = '' then Exit; //=>

  tmpStream := TMemoryStream.Create;
  try
    if LoadStream(tmpStream, pmcImageName, FPassword) then
    begin
      tmpStream.Position := 0;
      try
        pmImage.Picture.LoadFromStream(tmpStream);
        Result := True;
      except
      end;
    end;
  finally
    tmpStream.Free;
  end;
end;

procedure TImageResourceFile.SaveImage(pmImage: TImage; const pmcImageName: String);
var
  tmpStream: TMemoryStream;
begin
  if pmImage = Nil then Exit; //=>
  if pmcImageName = '' then Exit; //=>

  tmpStream := TMemoryStream.Create;
  try
    pmImage.Picture.SaveToStream(tmpStream);
    SaveImage(tmpStream, pmcImageName);
  finally
    tmpStream.Free;
  end;
end;

procedure TImageResourceFile.SaveImage(pmImageData: TCustomMemoryStream; const pmcImageName: String);
begin
  if pmImageData = Nil then Exit; //=>
  if pmcImageName = '' then Exit; //=>

  SaveStream(pmImageData, pmcImageName, FPassword, False);
end;
Anwenden kannst du es so:
Delphi-Quellcode:
var
  timer: TPrecisionTimer;
  imgResFile: TImageResourceFile;
begin
  Image.Picture.Assign(Nil);

  timer.Start;
  imgResFile := TImageResourceFile.Create(ChangeFileExt(Application.ExeName, '.dat'), 'Thomas');
  try
    imgResFile.LoadImage(Image, 'ImageName');
    ShowMessage(Format('Total time: %s', [timer.Stop]));
  finally
    imgResFile.Free;
  end;
end;
Eine 2MB große AES verschlüsselte PNG Bilddatei aus einer Zip-Datei in ein Image laden, benötigt im Beispiele eine Gesamtladezeit von ca. 60 ms. Das ist ein brauchbarer Wert. Davon dauert die Entschlüsselung ca. 5 ms. Wenn du viele Bilder auf einmal schreiben willst, solltest du dir eine BatchAdd Funktion schreiben.

mORMot musst du nicht installierten. Es reicht aus, die entsprechenden Bibliothekspfade einzufügen. Es steht eine ausführliche Hilfe, viele Beispiele (für mORMot1) und ein freundliches Forum zur Verfügung.
Bei einer neuen Anwendung würde ich mORMot2 empfehlen. Dazu gibt es zur Zeit noch keine Hilfe. Trotzdem hier der Link zum GitHub Repro.

Bis bald...
Thomas

mytbo 14. Jun 2022 11:04

AW: Memory Leak nach Zuweisung eines Resourcestream
 
Interessanter Nachtrag: Wenn man anstelle der Vcl.Imaging.pngimage besser die mormot.ui.gdiplus Unit verwendet, verkürzt sich das Speichern eines 2MB großen PNG-Bildes mit SaveImage() auf 9 ms gegenüber 900 ms mit der Delphi Unit und ein LoadImage() wird in 10 ms anstatt 80 ms ausgeführt. Es lohnt sich, habe auch nichts anderes erwartet.

Bis bald...
Thomas


Alle Zeitangaben in WEZ +1. Es ist jetzt 19:13 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz