Einzelnen Beitrag anzeigen

blackdrake

Registriert seit: 22. Aug 2003
Ort: Bammental
618 Beiträge
 
Delphi 10.3 Rio
 
#1

Programm abrupt beenden ohne Memory Leaks

  Alt 10. Sep 2007, 19:07
Hallo.

Mein Programm benutzt Zipping und Verschlüsselungsroutinen. Dafür nutze ich die Units KAZip, DEC (enthält viele große Units) und ZLib (von Borland). Es kann vorkommen, dass der Endbenutzer auf die Idee kommt, ein großes Verzeichnis oder ein ganzes Laufwerk zu zippen und diese Aktion wieder abbrechen möchte, da sie zu lange dauert. Leider beachten die oben genannten Units nicht Application.Terminated, weswegen ich ziemliche Probleme bei der Terminierung der Applikation habe. Da mein Programm keine Enddateien überschreibt (sondern vorher im Tempverzeichnis puffert) und bei meinem Programm keine Benutzerdaten bzw. getane Arbeit verloren gehen können, würde ich gerne dem Benutzer die Möglichkeit geben, das Programm ohne Strg+Alt+Entf zu beenden.

Zur Demonstration sei folgende Methode gegeben:

Delphi-Quellcode:
procedure kann_lange_dauern;
begin
  repeat
    application.processmessages;
  until false;
end;
Diese Funktion soll durch die Unendlichschleife eine lang andauernde ZIP/Verschlüsselungsfunktion darstellen. Es sei gegeben, dass ich auf diese Funktion nicht zugreifen oder sie verändern kann (ich habe keine Lust, DEC, KAZIP und ZLIB für application.terminated umzuschreiben).

Ich habe folgende Ideen gehabt und versucht:

1. Idee: Ressourcen freigeben und Exceptions verschlucken

Im Prinzip hat es funktioniert, aber es ist sehr unprofessionell, da ich Exceptions verschlucke.

Delphi-Quellcode:
var
  mystream: TStream;

procedure TForm1.Button1Click(Sender: TObject);
begin
  try
    // Wird bei Programmende durch eine Exception beendet, da wir MyStream freigeben.
    kann_lange_dauern(mystream);
  except
  end;
end;

procedure TForm1.OnClose(Sender: TObject; var Action: TCloseAction);
begin
  try
    mystream.free;
  except
  end;
end;
Da hier Exceptions verschluckt werden und es interne Exceptions gibt, ist diese Variante extrem unprofessionell.

2. Idee: Threads verwerden

Jetzt bin ich auf die Idee gekommen, Threads zu verwenden. Ich habe für jede ZIP/DEC-Aktion eine Thread-Klasse geschrieben, die sich den Thread bei Programmende automatisch per ExitThread bzw ExitProcess killt. Leider entstehen durch das abrupte Abbrechen des Threads MemoryLeaks.

Delphi-Quellcode:
type
  TKaZipExtract = class(TThread)
  private
    FExecuteFinished: boolean;
    FKaZIP: TKaZIP;
    FZipFile: string;
    FDestination: string;
  protected
    procedure Execute; override;
  public
    constructor Create; virtual;
    function RunAndFree(const KaZIP: TKaZIP; const ZipFile, Destination: string): boolean;
  end;

{ --- --- --- TKaZipExtract --- --- --- }

procedure TKaZipExtract.Execute;
begin
  FKaZIP.close;

  if fileexists(FZipFile) then
  begin
    FKaZIP.Open(FZipFile);
    try
      FKaZIP.ExtractAll(FDestination);
    finally
      FKaZIP.Close;
    end;
  end;

  FExecuteFinished := true;
end;

constructor TKaZipExtract.Create;
begin
  inherited create(true);
end;

function TKaZipExtract.RunAndFree(const KaZIP: TKaZIP; const ZipFile, Destination: string): boolean;
begin
  FKaZIP := KaZIP;
  FZipFile := ZipFile;
  FDestination := Destination;

  FExecuteFinished := false;

  try
    if Suspended then Resume;

    repeat
      Application.ProcessMessages;
    until FExecuteFinished or Application.Terminated or Terminated;

    result := not (Application.Terminated or Terminated);
    if not result then ExitProcess(handle); // Vorher: ExitThread
  finally
    free;
  end;
end;

function KaZIPExtract(const KaZIP: TKaZIP; const ZipFile, Destination: string): boolean;
var
  tmp: TKaZipAdd;
begin
  tmp := TKaZipAdd.Create;
  result := tmp.RunAndFree(KaZIP, ZipFile, Destination);
end;
3. Idee: ExitProcess verwenden

Ich kam auf die Idee, das gesamte Programm durch ExitProcess bei dem Event TForm1.OnClose zu beenden. Ich habe außerdem geprüft, ob MemoryLeaks dabei entstehen. Seltsamerweiße hat mir weder der BDS 2006 Memory Manager noch FastMM ein MemoryLeak gemeldet.

Test mit halt(), das ExitProcess gleicht.

Delphi-Quellcode:
procedure TForm1.OnCreate(Sender: TObject);
begin
  ReportMemoryLeaksOnShutDown := true; // ab BDS 2006
  TObject.Create; // Provozierter Memory Leak
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  kann_lange_dauern();
end;

procedure TForm1.OnClose(Sender: TObject; var Action: TCloseAction);
begin
  halt; // Warnung bzgl. Memory Leaks
end;
Test mit ExitProcess:

Delphi-Quellcode:
procedure TForm1.OnCreate(Sender: TObject);
begin
  ReportMemoryLeaksOnShutDown := true; // ab BDS 2006
  TObject.Create; // Provozierter Memory Leak
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  kann_lange_dauern();
end;

procedure TForm1.OnClose(Sender: TObject; var Action: TCloseAction);
begin
  ExitProcess(ExitCode); // KEINE Warnung bzgl. Memory Leaks
end;
Da ich bei ExitProcess keine Memory-Leak-Warnung erhielt, dachte ich, dass diese WinAPI-Methode den Arbeitsspeicher automatisch aufräumt. Auch bei MSDN ( http://msdn2.microsoft.com/en-us/library/ms682658.aspx ) wird bei ExitProcess nicht vor eventuell auftretenden MemoryLeaks gewarnt. Laut MemProof soll mein Programm durch ExitProcess(0) über 1000 MemoryLeaks besitzen. Ob Windows nun automatisch bei ExitProcess den Arbeitsspeicher aufräumt, weiß ich leider nicht.

4. Idee: Den Benutzer fragen, ob er MemoryLeaks will

Da ich nun absolut keine Ideen habe, muss ich wohl den Benutzer beim OnCloseQuery-Ereignis fragen, ob er den laufenden Prozess wirklich beenden möchte und das Risiko eingeht, dass Rückstände im Arbeitsspeicher bleiben könnten.

Delphi-Quellcode:
procedure TForm1.OnCloseQuery(Sender: TObject; var CanClose: Boolean);
var
  res: integer;
begin
  if programm_arbeitet then
  begin
    res := Application.MessageBox('Warnung', 'Das Programm arbeitet derzeit. Möchten Sie den Prozess beenden und die Gefahr auf sich nehmen, dass Rückstände im Arbeitsspeicher verbleiben?', MB_YESNO + MB_ICONEXCLAMATION);

    CanClose := res = ID_YES;
  end
  else
  begin
    CanClose := true;
  end;
end;

procedure TForm1.OnClose(Sender: TObject; var Action: TCloseAction);
begin
  ExitProcess(ExitCode);
end;
Hat irgendjemand eine bessere Idee, wie ich mein Programm abrupt ohne Memory Leaks beenden kann?

Gruß
blackdrake
Daniel Marschall
  Mit Zitat antworten Zitat