Delphi-PRAXiS
Seite 2 von 2     12   

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi Programm abrupt beenden ohne Memory Leaks (https://www.delphipraxis.net/99322-programm-abrupt-beenden-ohne-memory-leaks.html)

blackdrake 11. Sep 2007 06:47

Re: Programm abrupt beenden ohne Memory Leaks
 
Hallo.

Zitat:

Zitat von negaH
was auch ziemlich dämlich wäre da du damit diese Source abhängig machst von einer rießigen VCL

Ja, ich weiß, dass eine Unit nicht auf Forms/Application zugreifen sollte. Ich habe ja nicht gesagt, dass die Units dieses Feature bräuchten oder dass es sinnvoll sei, dass unabhängige Units application.terminated berücksichtigen.

Zitat:

Zitat von negaH
1.) ein Variable FAbort:Boolean in dein TForm einbauen
2.) deine Methode die alles macht per try except Block schützen und auf EAbort reagieren
3.) einen Button auf's TForm der FAbort auf TRUE setzt
4.) in deiner Methode .Process() bei FAbort = TRUE die Prozedure Abort aufrufen

Danke für die Tipps. Die Progress-Klasse verwende ich bereits folgendermaßen, um den Fortschritt in einer Gauge darzustellen:

Delphi-Quellcode:
TProgress = class(TInterfacedObject, IDECProgress)
  procedure Process(const Min, Max, Pos: Int64); stdcall;
  constructor Create;
  destructor Destroy; override;
end;
Ich verstehe aber nicht ganz, was du mit Punkt #2 meinst. Wie soll ich auf FAbort reagieren? Wie bekomme ich es hin, dass FAbort dazu führt, dass TCipher_xyz.EncodeFile etc. abbricht? Die einzigste Methode die mir einfällt, ist den Stream freizugeben und die Exceptions zu verschlucken (siehe Idee #1 ganz oben). Das scheint mir aber ziemlich unprofessionell, weil ich Speicher freigebe, der noch genutzt wird, um so die laufende Aktion zu terminieren. Außerdem verschlucke ich Exceptions (siehe http://www.delphipraxis.net/internal...=206667#206667 , Sünde #1). Wie soll ich anders auf FAboirt reagieren?

Das mit den TStream Descants verstehe ich auch nicht ganz. Das es eine Callback-Funktion gibt, verstehe ich. Aber wie gehe ich bei diesem Descants bei einer Termination vor? Gebe ich dann auch den Stream einfach frei und verschlucke die Exceptions, wenn ich eine Termination des Programmes wünsche?

Sobald ich wieder zuhause bin, probiere ich mal mit Delphi das ganze aus.

Nachtrag:

Zitat:

Zitat von negaH
1.) eine OnClick() muß reentrant werden, zb. durch Disablen des Buttons oder durch ein Locking

Verstehe leider nicht, was du meinst. Im ersten Beitrag habe ich Button1OnClick als Beispiel einer Benutzeraktion verwendet.

Zitat:

Zitat von negaH
2.) OnCloseQuery des Form benutzen und dort erst dann TRUE zurückliefern wenn deine Berechnungsfunktion/Loop beendet wurde.

Erstmal muss ich es schaffen, die laufende DEC/KaZIP/ZLIB Aktion zuende zu führen. Dann muss ich gucken, wie ich ermitteln kann, ob das Programm gerade beschäftigt ist, oder nicht.

Gruß
blackdrake

negaH 11. Sep 2007 07:10

Re: Programm abrupt beenden ohne Memory Leaks
 
Die Sache ist wirklich sehr einfach.

Delphi-Quellcode:
type
  TForm1 = class(TForm, IDECProgress)
    procedure ButtonClick(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  private
    FAction: Integer;
    procedure Process(const Min,Max,Pos: Int64); stdcall;
  end;


procedure TForm1.ButtonClick(Sender: TObject);
begin
  if FAction = 0 then
  try
    Inc(FAction);
    Button.Caption := 'Abort';
    try
      with TCipher_Blowfish.Create do
      try
        Init(...);
        EncodeFile(FileName, FileName, Self);
      finally
        Free;
      end;
    except
      on E: Exception do
        if E is EAbort then ShowMessage('aborted by User')
          else reraise;
    end;
  finally
    FAction := 0;
    Button.Caption := 'Start';
  end else Inc(FAction);
end;

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := FAction = 0;
  if not CanClose then Inc(FAction);
end;

procedure TForm1.Process(const Min,Max,Pos: Int64);
begin
  Application.ProcessMessages;
  if FAction > 1 then Abort;
end;
Wenn FAction == 0 ist dann kann der Anwender beim KLick auf Button eine Datei verschlüsseln. In diesem Moment wird FAction == 1 sein. Über die .Process() Methode, quasi eine Callback, ruft nun der Cipher periodisch auf. Darin wird Application.ProcessMessages; aufgrufen, der User kann also den Button nochmals drücken, dessen Caption zeigt ja Abort an. Drück er nochmals drauf dann landet man im .OnClick() auf Grund der Anfrag if FAction = 0 then eben nicht in der Verschlüsselung sondern beim Inc(FAction). Aus FAction == 1 wird dann FAction > 1. Sobald nun FAction > 1 ist wird in der .Process() Callback eine Exception ausgelösst, eben Abort eine stille Exception. Da der Cipher intern dieses .Process() aufruft und wir dadrinnen eine Exception auslössen wird Cipher.EncodeFile() verlassen, abgebrochen. Man landet dann im finally end Block und von dort im except end Block in dem wir auf EAbort gezielt reagieren.

So sollte man mit Schutzblöcken und gezielter Exceptionbehandlung in Delphi arbeiten.

In OnCloseQuery fragen wir noch ab ob gerade FAction = 0 ist, also keine Aktion gerade läuft. Wenn nicht dann lösen wir auch hier einen Abort indirekt aus. Der Benutzer muß dann meistens 2 mal den Closebutton der Titelzeile klicken.

Ziemlich simpel die Sache, oder ?

Wenn du jetzt statt dem TForm ein TThread benutzt dann müsste nur FAction entfernt werden und .Process() kein Application.ProcessMessages drinnen stehen. Dafür aber
Delphi-Quellcode:
if Terminated then Abort;
.

Gruß Hagen

blackdrake 11. Sep 2007 07:17

Re: Programm abrupt beenden ohne Memory Leaks
 
Hallo Hagen.

Danke für die schnelle und ausführliche Antwort. Ich probiere es zuhause gleich aus. Anschließend versuche ich es noch mit den restlichen ZIP/Compress-Funktionen mittels der TStream-Descants und melde mich, ob es funktioniert hat oder nicht.

Gruß
blackdrake

negaH 11. Sep 2007 07:25

Re: Programm abrupt beenden ohne Memory Leaks
 
Also DEC hält sich strikt intern daran und verwendet immer schön try finally end; Schutzblöcke wenn es Resource alloziert hat. Es ist jetzt für dich nur wichtig nachzuschauen ob das die beiden anderen Libs auch so handhaben. Wenn ja, können bei dieser Methode keine Memoryleaks entstehen, der User hat die Kontrolle die Aktion abzubrechen, und die Anwendung wird ganz sauber beendet so wie vorgesehen.

Keine Holzhammer Methode mehr notwendig indem du einen Thread oder Prozess abschießt. Auch die Anwendung bleibt weiter quasi bedienbar. Einzigstes Problem, sollte der Anwender zb. in die Titelzeile klicken und die Maustaste gedrückt lassen so wird der aktuelle Verschl. Vorgang temporär angehalten. Das kannst du nur mit Threads umgehen.

Übrigens durch die

if FAcxtion = 0 then

Abfrage wird die OnClick Methode eben reentrant, sie kann quasi rekusiv merhfach afgerufen werden.

Gruß Hagen

blackdrake 11. Sep 2007 22:10

Re: Programm abrupt beenden ohne Memory Leaks
 
Hallo.

Bei DEC hat es mit abort; und dem Progress-Interface sehr gut funktioniert.

Aber bereits bei ZLib klappt es gar nicht:

Delphi-Quellcode:
procedure TForm1.zlib_progress(Sender: TObject; tag: Integer; Percent: Integer);
begin
  pbr_progress.position := Percent * pbr_progress.max div 100;
  application.processmessages;
  if application.Terminated then SysUtils.abort;
end;

procedure CompressFile(InputFileName, OutputFileName: string; Progress: TOnProgress);
var
  CompressInputStream: TStreamProgressAdapter; // TFileStream
  CompressOutputStream: TFileStream;
  CompressionStream: ZLib.TCompressionStream;
begin
  try

  CompressInputStream := TStreamProgressAdapter.Create(TFileStream.Create(InputFileName, fmOpenRead), 0);
  CompressInputStream.OnProgress := Progress;
  try
    CompressOutputStream := TFileStream.Create(OutputFileName, fmCreate);
    try
      CompressionStream := TCompressionStream.Create(clMax, CompressOutputStream);
      try
        CompressionStream.CopyFrom(CompressInputStream, CompressInputStream.Size);
      finally
        CompressionStream.Free;
      end;
    finally
      CompressOutputStream.Free;
    end;
  finally
    CompressInputStream.Free;
  end;

  except
    on E: Exception do
      if not (E is EAbort) then raise;
  end;
end;

begin
  CompressFile('...', '...', Form1.zlib_progress);
end.
Ich habe bereits versucht, den Adapter alternativ für CompressOutputStream oder CompressionStream zu verwenden. Bei CompressionStream gab es eine Stream-Operation-Exception. CompressInputStream müsste jedoch laut des Beispielprogrammes die korrekte Wahl sein. Wenn ich jetzt meine Anwendung schließe, scheint es einen Dead-Lock zu geben. Die Progressbar bewegt sich nicht mehr, aber die Methode CompressFile() wird nicht über die Exception verlassen, sondern bleibt aktiv. :(

Die Methode CompressFile() würde erst durch die EAbort-Exception verlassen werden, wenn ich direkt vor CompressionStream.CopyFrom ein "abort;" setzen würde. Irgendwie wird das Abort nicht weitergereicht.

Gruß
blackdrake

negaH 11. Sep 2007 23:06

Re: Programm abrupt beenden ohne Memory Leaks
 
Ach mann, nimm das blöde Application.Terminate raus und baue die ButtonOnClick() Methode wie in meinem obigen Beispiel auf. Statt .Process() vom IDECPrograess Interface dann das Gleiche mit dem OnProgress Event des Streams. Im ButtonClick() dann statt meinem TCipher Object eben deine CompressFile() Funktion aufrufen.

Gruß Hagen

blackdrake 15. Sep 2007 22:12

Re: Programm abrupt beenden ohne Memory Leaks
 
Hallo.

Habe jetzt endlich wieder Zeit gefunden, mich mit dem Problem zu beschäftigen und konnte es lösen. Außerdem verstehe ich nun die genaue Funktionsweise von Abort(). Der Aufruf von Abort() führt zu der Exception EAbort, die dann durchgeschleift wird und alle Aktionen der Methoden in der Aufrufhierarchie beendet bzw. zu deren except Teil der try..except's führt. In meinem letzten Post war die Progress-Methode nicht innerhalb der Hierarchie und deswegen schlug Abort() fehl (glaube ich).

@negaH: Bei der Entwicklung vermeide ich es in der Regel, zu viele Baustellen auf einmal aufzumachen. Deswegen war in meinem vorherigen Code noch Application.Terminated anstatt WartenForm.FAbort. Bei meinem Programm hat der Verschlüsselungsbutton keine Re-Entranz, da das Form beim Klicken unsichtbar wird und ein Wartenformular mit einem Abbrechenbutton erscheint. Da Start/Stop in sofern auf 2 Buttons aufgeteilt ist, ist FAction unnötig.

Für die, die eventuell das selbe Problem haben, füge ich die Lösung nun an.

KaZIP selbst stellt keine Streams zur Verfügung. Doch nach kurzer Überlegung mit ich auf die Idee gekommen, Abort() in die On(De)Compress/OnAdd-Methoden von KaZIP zu packen:

Delphi-Quellcode:
procedure TMainForm.kazip_add(Sender: TObject; ItemName: string);
begin
  application.processmessages;
  if WartenForm.FAbort then sysutils.Abort;
end;
Um die ZLib Aktionen abzubrechen, habe ich die Progress-Methode in eine eigene Klasse gepackt:

Delphi-Quellcode:
type
  TZLIBProgress = class(TObject) // Ist die Ableitung von TObject eigentlich hierfür sinnvoll?
  public
    procedure Progress(Sender: TObject; tag: Integer; Percent: Integer);
    constructor Create;
    destructor Destroy; override;
  end;

{ ************************ TZLIBProgress ************************ }

procedure TZLIBProgress.Progress(Sender: TObject; tag: Integer; Percent: Integer);
begin
  WartenForm.pbr_progress.Position := Percent * WartenForm.pbr_progress.max div 100;
  Application.ProcessMessages;
  if WartenForm.FAbort then SysUtils.abort;
end;

constructor TZLIBProgress.Create;
begin
  inherited Create;
end;

destructor TZLIBProgress.Destroy;
begin
  inherited Destroy;
end;
Die Anwendung:

Delphi-Quellcode:
function DeCompressFile(InputFileName, OutputFileName: string): boolean;
var
  CompressInputStream: TFileStream;
  CompressOutputStream: TStreamProgressAdapter;
  DeCompressionStream: ZLib.TDeCompressionStream;
  CProgress: TCommonProgress;

  Buf: array[0..4095] of Byte;
  Count: Integer;
begin
  result := true;
  CompressInputStream := TFileStream.Create(InputFileName, fmOpenRead);
  try
    CompressOutputStream := TStreamProgressAdapter.Create(TFileStream.Create(OutputFileName, fmCreate), 0);

    try
      DecompressionStream := TDecompressionStream.Create(CompressInputStream);
      try
        CProgress := TCommonProgress.Create;
        try
          try
            CompressOutputStream.OnProgress := CProgress.Progress;
            while true do
            begin
              Count := DeCompressionStream.Read(Buf[0], SizeOf(Buf));
              if Count = 0 then
                break
              else
                CompressOutputStream.Write(Buf[0], Count);
            end;
          except
            on E: Exception do
            begin
              result := false;
              if not (E is EAbort) then raise;
            end;
          end;
        finally
          CProgress.Free;
        end;
      finally
        DecompressionStream.Free;
      end;
    finally
      CompressOutputStream.Free;
    end;
  finally
    CompressInputStream.Free;
  end;
end;

function CompressFile(InputFileName, OutputFileName: string): boolean;
var
  CompressInputStream: TStreamProgressAdapter;
  CompressOutputStream: TFileStream;
  CompressionStream: ZLib.TCompressionStream;
  CProgress: TCommonProgress;
begin
  result := true;
  CompressInputStream := TStreamProgressAdapter.Create(TFileStream.Create(InputFileName, fmOpenRead), 0);
  try
    CompressOutputStream := TFileStream.Create(OutputFileName, fmCreate);
    try
      CompressionStream := TCompressionStream.Create(clMax, CompressOutputStream);
      try
        CProgress := TCommonProgress.Create;
        try
          try
            CompressInputStream.OnProgress := CProgress.Progress;
            CompressionStream.CopyFrom(CompressInputStream, CompressInputStream.Size);
          except
            on E: Exception do
            begin
              result := false;
              if not (E is EAbort) then raise;
            end;
          end;
        finally
          CProgress.Free;
        end;
      finally
        CompressionStream.Free;
      end;
    finally
      CompressOutputStream.Free;
    end;
  finally
    CompressInputStream.Free;
  end;
end;
Danke für euere Ratschläge! :-D

Gruß
blackdrake

[edit=SirThornberry]Code-Highlighting korrigiert - Mfg, SirThornberry[/edit]


Alle Zeitangaben in WEZ +1. Es ist jetzt 16:46 Uhr.
Seite 2 von 2     12   

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